출처
- Peter L Dordal, An introduction to Computer Networks, Release 2.0.11
전송 계층 프로토콜, TCP와 UDP
TCP와 UDP는 IP 계층 위에 얹어져 있는 표준 전송 프로토콜들이다.
UDP는 멀리 떨어져 있는 소켓(<host, port> 쌍)으로 데이터그램을 전달하는 간단한 전송 방법이다. TCP의 경우, "연결"된 소켓으로 데이터를 보내며 이때 UDP와 비교하여 더 풍부한 기능들을 제공한다.
TCP의 특징
TCP는 UDP와 비교했을 때 많은 부분에서 차이를 보인다.
- 스트림 지향적 stream-oriented
- 애플리케이션은 아주 작거나 아주 큰 양의 데이터를 쓸 수 있다. TCP 계층이 알아서 데이터를 적절하게 패킷화한다. (TCP는 메시지나 레코드를 전송하지 않는다. 바이트 스트림(연속된 형태의 데이터)을 전송한다.)
- 연결 지향적 connection-oriented
- 데이터 전송을 시작하기 전에 커넥션 connection이 수립 established하는 과정이 반드시 있어야 한다.
- 신뢰성 reliable
- TCP는 패킷 전송의 정확한 순서를 보장하기 위해 순서가 있는 숫자를 사용한다.
- 시간 초과 timeout/재전송 retransmission 메커니즘을 사용하여 대규모 네트워크 장애 상황이 아닌 이상 데이터 손실이 일어나지 않도록 보장한다.
- 슬라이딩 윈도우 알고리즘 sliding window
- 가능한 범위에서 최대치에 가까운 처리량을 달성하기 위해서 슬라이딩 윈도우 알고리즘을 사용한다.
TCP가 적합한 상황
TCP가 적합한 상황들이 많이 있다.
- 큰 크기의 파일을 전송할 때
- 양 엔드포인트가 작은 패킷의 스트림을 보내고 받는 상호작용적인 애플리케이션
- 매 키보드 입력마다 패킷이 교환되는 ssh나 telnet
- 초당 많은 쿼리가 수행되는 데이터베이스 커넥션
- 한 엔드포인트가 단일 메시지를 전송하고 다른 엔드포인트가 응답하면 커넥션이 종료되는 요청/응답 프로토콜
- 단점은 매 요청마다 새로운 커넥션을 set up하기 때문에 오버헤드가 발생한다는 것이다. 하나의 TCP 연결에 대해서 여러 요청/응답의 쌍을 허용한다면 더 나은 설계가 될 수도 있다.
TCP의 연결 지향성 connection-orientation과 신뢰성 reliability는 IP 계층이 제공하지 않는 기능으로, IP 계층 위에서 구축된 추상적 기능들이다.
TCP의 연결 지향성
UDP의 경우, 만약 서버가 소켓을 오픈하면 인터넷에 있는 클라이언트는 그 소켓의 주소를 통해 그 소켓으로 데이터를 전송할 수 있다. 따라서 모든 UDP 애플리케이션은 자신에게 도착하는 모든 패킷의 source address를 확인할 수 있도록 준비되어야 한다.
TCP의 경우, 연결된 소켓에 도착하는 모든 데이터는 연결의 반대편에 있는 엔드포인트로부터 와야만 한다. 서버 S가 처음 소켓 s를 오픈할 때, 소켓은“unconnected" 상태이다. 이것은 LISTEN 상태라고 표현되곤 한다. 이때의 소켓 또한 호스트와 포트 번호로 이루어진 소켓 주소를 가지긴 하지만 LISTENING 소켓은 절대 데이터를 수신받지 않는다. 만약 인터넷 어디인가에 있는 클라이언트 C가 소켓 s에 데이터를 전송하고 싶다면, 클라이언트 C는 먼저 소켓 s와 연결을 수립해야 한다. 이 연결은 S와 C 모두에서 소켓 주소들로 이루어진 소켓의 쌍으로 정의된다. 연결 프로세스의 과정에서 서버 S에서는 소켓 s의 자식 소켓인 sc가 새롭게 생성되고 연결될 것이다. 자식 소켓 sc는 C로부터 모든 데이터를 전송받게 된다. 그리고 서버는 sc와의 소통을 처리하기 위해 새로운 스레드나 프로세스를 생성한다. 일반적으로 서버는 소켓 s에 대하여 여러 개의 연결된 자식 소켓들을 가지며, 각 자식 소켓에 대하여 프로세스가 하나씩 할당된다.
만약 클라이언트 C1과 클라이언트 C2가 모두 s에 연결되어 있다면, 소켓 S에는 두 개의 연결된 소켓 s1, s2이 생성될 것이며 두 개의 별도의 프로세스가 생성될 것이다. 서버 S에 s의 소켓 주소로 패킷이 도착할 때, 서버 S에서는 source socket의 주소를 조사하여 데이터가 C1-S 연결의 부분인지 아니면 C2-S 연결의 부분인지 확인할 것이다. 그리고 이에 따라 s1 또는 s2에서의 읽기 read가 데이터를 보게 될 것이다.
TCP 헤더
아래는 TCP 헤더의 다이어그램이다.
- source port, destination port
- TCP 헤더에서 source 포트와 destination 포트는 UDP에서와 똑같이 16 비트이다.
- data offset
- 4비트 크기의 데이터 오프셋 필드는 헤더의 크기를 나타낸다. 32비트(4바이트) 크기 워드의 수로 나타낸다.
- 데이터 시작 위치를 알려준다. TCP 헤더가 끝나는 지점을 기준으로 데이터 세그먼트(payload)가 시작된다.
- 헤더에 옵션 정보가 없다면 기본적으로 data offset의 값은 5이다. (5 * 4바이트 = 20바이트)
- data offset은 4비트 크기이므로 TCP 헤더의 최대 길이는 (15 * 4바이트 = 60바이트)이다.
- checksum
- UDP와 마찬가지로, TCP의 체크섬 checksum 은 TCP 헤더, TCP 데이터, 그리고 IP 가상 헤더 pseudo header 를 포함한다. 이 가상 헤더는 출발지 및 목적지 IP 주소를 포함한다.
- NAT 라우터가 헤더 값을 수정할 경우, 체크섬도 반드시 업데이트되어야 한다.
- sequence number
- 시퀀스 번호와 확인 응답 번호는 데이터를 바이트 단위로 번호 매기기 위해 사용된다. 예를 들어, TCP는 데이터를 1024바이트 블록 단위로 전송할 경우, 시퀀스 번호를 1024씩 증가시킬 수 있다. 또는 1바이트짜리 Telnet 패킷을 전송할 경우, 시퀀스 번호는 1씩 증가한다.
- 상대적 값으로, 패킷의 첫 번째 바이트가 데이터 스트림에서 차지하는 위치를 나타낸다.
데이터가 없는 경우, 전송될 첫 번째 바이트의 위치를 의미합니다.
데이터 패킷과 ACK 패킷의 구분이 없음
TCP에서는 DATA 패킷과 ACK 패킷을 구분하지 않습니다.
A에서 B로 데이터를 전송하는 모든 패킷에는 B에서 A로 받은 데이터의 최신 확인 응답 번호(ACK)도 포함됩니다.
많은 TCP 애플리케이션은 데이터 흐름이 대체로 단방향적으로 이루어집니다.
이 경우, 송신자는 각 패킷에 동일한 ACK 번호를 포함하고,
수신자는 각 패킷에 동일한 시퀀스 번호를 포함하게 됩니다.
TCP 데이터 세그먼트(Segment)
TCP 패킷의 데이터 부분은 전통적으로 세그먼트(segment)라고 불린다.
시퀀스 번호와 확인 응답 번호의 의미
초기 시퀀스 번호(ISN)
실제로 전송되는 시퀀스 번호와 확인 응답 번호는 상대적 값에 초기 시퀀스 번호(ISN, Initial Sequence Number)를 더한 값으로 표현된다. 연결의 양방향은 각각 고유한 ISN을 사용하며, 이는 연결의 수명 동안 고정된다.
누적 방식 확인 응답
TCP 확인 응답(ACK)은 누적 방식으로 이루어집니다.
예: ACK 번호 N은 N보다 작은 모든 데이터 바이트가 수신되었음을 확인하는 것을 의미합니다.
부분적인 확인 응답은 지원되지 않습니다.
예를 들어, 패킷 1, 2, 3, 5를 수신한 경우, ACK는 패킷 3까지 누적 확인할 수 있으며, 패킷 5만 별도로 ACK할 수는 없습니다.
TCP 연결 설정: 3-Way Handshake
- TCP 연결은 3-Way Handshake로 알려진 교환을 통해 설정됩니다.
- A가 클라이언트이고 B가 LISTEN 상태의 서버라면, 핸드셰이크는 다음과 같이 진행됩니다:
- A가 B에게 SYN 플래그가 설정된 패킷(SYN 패킷)을 보냅니다.
- B는 SYN 플래그와 ACK 플래그가 모두 설정된 패킷(SYN-ACK 패킷)으로 응답합니다.
- A는 B의 SYN에 대한 ACK 패킷으로 응답합니다.
- A가 클라이언트이고 B가 LISTEN 상태의 서버라면, 핸드셰이크는 다음과 같이 진행됩니다:
- 보통 3-Way Handshake는 애플리케이션이 연결 요청을 보낼 때 트리거됩니다. 데이터 전송은 핸드셰이크가 완료된 후에만 가능합니다.
- 이 과정에서 데이터 전송이 지연되므로, 데이터 전송 전에 1 RTT(왕복시간)가 필요합니다.
- TCP의 초기 표준인 RFC 793은 첫 번째 SYN 패킷과 함께 데이터를 전송하는 것을 허용하지만, 이 데이터는 핸드셰이크가 완료될 때까지 상대 애플리케이션에서 사용할 수 없습니다.
- 대부분의 TCP 프로그래밍 인터페이스는 이 초기 데이터 전송을 지원하지 않습니다.
- TCP가 핸드셰이크 중에 데이터 전송을 지원해야 한다는 요구가 주기적으로 제기됩니다. 이는 **RPC(Remote Procedure Call)**와 유사한 요청/응답 시간을 달성하려는 목적입니다. 자세한 내용은 18.5 TCP Faster Opening에서 다룹니다.
SYN 플러딩 공격(SYN Flooding Attack)
- 3-Way Handshake는 SYN 플러딩 공격에 취약합니다.
- 공격자는 서버 B에게 대량의 SYN 패킷을 보냅니다.
- 각 SYN 패킷에 대해 B는 합법적인 연결 요청으로 간주하고 리소스를 할당해야 합니다.
- 충분히 많은 요청이 들어오면 B의 리소스가 고갈될 수 있습니다.
- SYN 플러딩은 스푸핑된(Spoofed) SYN 패킷(출발지 IP 주소가 위조되고 추적할 수 없는 경우)으로 수행되면 특히 더 쉽습니다.
- 더 큰 규모의 SYN 플러딩은 실제 클라이언트로부터 발생할 수도 있지만, 이는 공격자의 리소스 소모가 훨씬 큽니다.
- SCTP(18.15.2 SCTP)와 같은 프로토콜은 이러한 SYN 플러딩, 특히 스푸핑된 SYN으로부터 완화할 수 있도록 설계되었습니다.
TCP 연결 종료: FIN 교환
- 연결 종료는 FIN 패킷을 포함하는 교환을 통해 이루어지며, 과정은 다음과 같습니다:
- A가 B에게 FIN 플래그가 설정된 패킷(FIN 패킷)을 보내, 더 이상 데이터를 보내지 않을 것임을 알립니다.
- B는 A의 FIN을 ACK로 확인합니다.
- B는 여전히 A에게 추가 데이터를 보낼 수 있습니다.
- B가 데이터 전송을 종료할 준비가 되면, FIN 패킷을 A에게 보냅니다.
- A는 B의 FIN에 대한 ACK로 응답합니다. (이것이 마지막 패킷입니다.)
- 이 FIN 교환은 실제로 두 개의 별도 양방향 FIN/ACK 핸드셰이크와 유사합니다.
RST 패킷
- **RST(Reset)**는 어느 쪽이든 언제든지 연결을 강제로 종료할 수 있습니다.
- A가 SYN 패킷을 보내기 전에 데이터를 전송하거나,
- B가 SYN에 대한 응답을 보내기 전에 LISTEN 상태가 아니라면,
- B는 RST 패킷을 전송하여 "연결 거부(connection refused)"를 알립니다.
- 때때로 네트워크 경로 상의 라우터는 스푸핑된 RST 패킷을 보내 TCP 연결을 강제로 종료할 수 있습니다.
- 외부 공격자가 스푸핑된 RST 패킷을 사용해 TCP 연결을 종료하려면, 포트 번호와 현재 SEQ 값을 추측해야 합니다.
- 윈도우 크기가 클수록(예: 4 MB) SEQ 값 추측이 쉬워져 공격이 더 가능해집니다.
PSH 비트
- A가 작은 패킷을 여러 번 B로 보낸다면, B는 이를 모아 I/O 버퍼로 조립한 뒤 애플리케이션으로 전달할 수 있습니다.
- 그러나 PSH(Push) 비트가 설정된 패킷은 즉시 애플리케이션으로 전달되어야 합니다.
- BSD 소켓 라이브러리에서는 이 비트를 직접 설정할 수 없으며, 쓰기 작업마다 자동으로 설정됩니다.
- 다만, 송신 측이 비트를 설정하지 않거나 여러 쓰기 작업을 하나로 병합할 수도 있어, PSH 비트를 레코드 구분자로 사용하는 것은 어렵습니다.
URG 비트
- URG(Urgent) 비트는 긴급 데이터를 즉시 처리할 때 사용됩니다.
- 예: A가 많은 데이터를 B로 전송하고 있는데, A가 **CNTL-C(중단 요청)**를 보냈다고 가정합니다.
- 일반적으로 B는 기존 데이터를 모두 처리한 후에야 CNTL-C를 처리할 수 있지만, URG 비트를 사용하면 CNTL-C를 비동기적으로 즉시 전달할 수 있습니다.
- TCP 헤더의 Urgent Pointer 필드는 해당 패킷에서 긴급 데이터의 위치를 나타냅니다.
- RFC 6093에서는 URG 비트와 관련된 혼란을 다루며, 개발자가 이 기능을 피할 것을 권장합니다.
TCP 연결 닫기
일반적인 연결 닫기 시퀀스는 다음과 같다.
A의 FIN은 B에게 더 이상 데이터를 보내지 않을 것이라는 약속입니다.
하지만 A는 여전히 B로부터 데이터를 받을 준비를 해야 하며, 이는 다이어그램에서 선택적 데이터(optional data)로 표시됩니다.
- 예를 들어, A가 데이터를 B로 스트리밍하여 정렬을 요청하는 경우가 있습니다.
- A는 데이터를 모두 보낸 후, FIN을 전송하여 전송이 완료되었음을 알립니다.
- 그제야 B는 데이터를 정렬하고 A에게 다시 보냅니다.
- 다만, A의 FIN 이후 B에서 A로 선택적 데이터가 전송되는 경우는 상대적으로 드뭅니다.
TCP 연결 종료 과정
- A는 B에게 FIN을 보내고 ACK를 받습니다.
- 이후 B는 A에게 FIN을 보내고 ACK를 받습니다.
- 이 과정은 두 개의 별도 양방향 종료 핸드셰이크를 구성합니다.
능동 종료(Active CLOSE)와 수동 종료(Passive CLOSE)
- TCP 연결에서는 어느 쪽이든 먼저 연결을 닫을 수 있습니다.
- 이는 전화 통화에서 어느 한쪽이 먼저 전화를 끊는 것과 유사합니다.
- 능동 종료(Active CLOSE): 먼저 FIN을 보내는 쪽.
- 다이어그램에서 A는 능동 종료를 수행합니다.
- A는 상태를 다음과 같이 변경합니다:
- ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 (B가 FIN을 ACK한 후) → TIME_WAIT → CLOSED.
- 수동 종료(Passive CLOSE): FIN을 나중에 받는 쪽.
- 다이어그램에서 B는 수동 종료를 수행합니다.
- B는 상태를 다음과 같이 변경합니다:
- ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED.
동시 종료(Simultaneous Close)
- 동시 종료: 양쪽이 서로의 FIN을 받기 전에 FIN을 동시에 보내는 경우.
- 이는 동시 열기(Simultaneous Open)보다 약간 더 자주 발생하지만 여전히 드뭅니다.
- 이 경우:
- 양쪽 모두 FIN을 보내고 FIN_WAIT_1 상태로 이동합니다.
- 서로의 FIN을 받은 후, 최종 ACK를 보내고 CLOSING 상태로 이동합니다.
절반 종료(Half-Close)
- TCP 연결은 절반 종료(Half-Close) 상태가 될 수 있습니다.
- 이는 한쪽에서 FIN을 보내 더 이상 데이터를 보내지 않겠다고 약속했지만,
- 상대방의 FIN을 기다리는 경우입니다.
- 다이어그램에서 A는 FIN_WAIT_1 및 FIN_WAIT_2 상태에 있을 때 절반 종료 상태입니다.
- BSD 소켓 라이브러리에서는 shutdown() 호출을 통해 절반 종료를 수행할 수 있습니다.
- 이는 한쪽에서 FIN을 보내 더 이상 데이터를 보내지 않겠다고 약속했지만,
절반 열기(Half-Open)
- 절반 열기(Half-Open)는 연결 종료 프로토콜과 무관합니다.
- 한쪽이 ESTABLISHED 상태에 있지만,
- 통신이 잠시 중단된 사이에 상대방이 재부팅된 경우를 의미합니다.
- 이 상태에서 ESTABLISHED 측에서 패킷을 전송하면, 재부팅된 측은 RST로 응답하며 연결이 완전히 종료됩니다.
- 한쪽이 ESTABLISHED 상태에 있지만,
'정글' 카테고리의 다른 글
UDP (0) | 2024.12.19 |
---|---|
WSGI, Gunicorn (0) | 2024.12.18 |
OpenSSL와 자체 서명 인증서 (0) | 2024.12.18 |
Nginx (1) | 2024.12.18 |
PINTOS 4주차 Stack Growth (0) | 2024.10.22 |