0. Contents


1. Overview

HTTPGET / HTTP/1.1용건 — 3.2포트를 붙인다TCP58676 → 8080 · seq/ack빠짐없이 순서대로 — 4장 · TLS 로 잠그면 5장 · UDP 면 6장주소를 붙인다IP127.0.0.1 → 127.0.0.1 · TTL주소 — 밖으로 나가는 이야기는 01-2통로 표시를 붙인다Loopback (lo0)4바이트 표시 · MAC 없음한 컴퓨터 안이라 선을 타지 않는다
curl 한 번은 네 겹으로 포장된다 — 이 편은 그 상자를 컴퓨터 한 대 안(lo0)에서 뜯어본다

1장이 통째로 네트워크 기초인 까닭

  • 쿠버네티스 네트워크 책인데 1장은 통째로 네트워크 기초다
  • 처음엔 돌아가는 길 같지만, 다 읽고 나면 이유가 보인다
  • Pod, Service, CNI 는 새로운 네트워크가 아니다 → TCP, IP, NAT, 라우팅을 조합한 것이다
  • 그래서 장애가 나면 결국 이 기초 중 하나를 들여다보게 된다

1장 구성: 두 편

  • 이 글(01-1)은 컴퓨터 한 대 안의 이야기다
    • macOS 노트북에 작은 Go 서버를 띄우고 curl 을 한 번 보낸다
    • 그 요청이 어떤 모양으로 오가는지 tcpdump 로 찍어서 뜯어본다
    • 같은 요청을 TLS 로 잠가 보고, UDP 로 바꿔 본다
  • 상자가 컴퓨터 밖으로 나가는 이야기 — IP, 라우팅, ARP, VXLAN, 쿠버네티스 — 는 01-2 에서 이어진다

2. OSI Model — 외울 목록이 아니라 고장 위치 지도

2.1 일곱 층과 캡슐화 — 위에서 씌우고 아래에서 벗긴다

층으로 나눠 보는 이유 ①: 고장 위치 좁히기

  • 고장 난 곳을 좁힐 수 있다
  • “인터넷이 안 된다” 를 “몇 층이 안 된다” 로 바꿔 말할 수 있으면 반은 푼 것이다
L7 응용 · HTTP연결은 되는데 503 이 온다L4 전송 · TCP, UDP주소까지는 닿는데 포트 연결이 안 된다L3 네트워크 · IP목적지로 가는 길이 없다L2 링크 · Ethernet, MAC같은 와이파이의 기기와도 안 된다L1 물리 · 선, 전파선이 빠졌다 · 와이파이가 끊겼다
증상을 층에 붙이면 볼 곳이 좁혀진다

층으로 나눠 보는 이유 ②: 층끼리의 독립

  • 층끼리 서로의 속을 몰라도 된다
  • IP 는 아래 선이 와이파이인지 광케이블인지 신경 쓰지 않는다
  • 그래서 한 층을 바꿔도 나머지는 그대로 쓸 수 있다

캡슐화: 한 겹씩 포장

  • 보낼 때는 위에서부터 한 겹씩 포장한다
  • 이걸 캡슐화라고 부른다
HTTPTCPHTTPIPTCPHTTPEthernetIPTCPHTTP보낼 때씌운다받을 때벗긴다
가장 나중에 씌운 바깥 상자가 가장 먼저 벗겨진다

패킷 속 세그먼트, 세그먼트 속 HTTP

  • 패킷 안에 TCP 세그먼트가 있고, 세그먼트 안에 HTTP 가 있는 구조다
  • 4.6 에서 이 상자를 실제로 뜯어본다

2.2 TCP/IP Model — 실제로는 네 층

실제 인터넷의 층 구성

  • 실제 인터넷은 일곱 층을 네 층으로 묶어서 쓴다
L7 응용L6 표현L5 세션L4 전송L3 네트워크L2 데이터 링크L1 물리ApplicationHTTP · DNS · TLSTransportTCP · UDPInternetIP · ICMPLinkEthernet · Wi-Fi · ARPOSI 7층TCP/IP 4층
L5·L6 은 앱과 TLS 에 녹아 있고, L1·L2 는 링크 하나로 묶인다

층 모델의 한계: 커널 안에 서랍은 없다

  • 층은 이해를 돕는 그림일 뿐이다
  • 리눅스 커널 안에 일곱 칸짜리 서랍이 있는 건 아니다
  • 그래서 한 층에 딱 들어가지 않는 기술도 있다 — eBPF 가 그렇다
    • eBPF 는 층이 아니라 커널의 원하는 지점에 코드를 꽂는 방법이다
    • 쿠버네티스에서는 주로 L3/L4 일을 맡긴다

3. Web Server — 같은 Hello 를 끝까지 따라간다

실습 서버: Go 로 만든 Hello 서버

  • 책은 Go 로 만든 작은 서버 하나를 장 끝까지 쓴다
  • 8080 포트에서 기다리다가 누가 오면 “Hello” 라고 답하는 게 전부다
  • 핵심은 이 한 줄이다
http.ListenAndServe("0.0.0.0:8080", nil) // 8080 포트를 열고 기다린다

3.1 0.0.0.0 — 주소가 아니라 모든 문

전제: 컴퓨터의 IP 는 하나가 아니다

  • 0.0.0.0 을 이해하려면 먼저 이걸 알아야 한다
ifconfig | grep "inet "

통로(인터페이스)마다 붙는 IP

  • 노트북에서 쳐 보면 여러 개가 나온다
    • 127.0.0.1(자기 자신)
    • 192.168.x.x(와이파이)
    • VPN 이나 도커 주소까지
  • IP 는 컴퓨터가 아니라 연결 통로(인터페이스)마다 하나씩 붙기 때문이다

서버가 정할 것: 받을 통로

  • 그래서 서버를 띄울 때는 어느 통로로 오는 연결을 받을지 정해야 한다
127.0.0.1:8080lo0 · 127.0.0.1열림en0 · 192.168.x.x닫힘utun · VPN닫힘같은 컴퓨터 안에서 온 것만192.168.x.x:8080lo0 · 127.0.0.1닫힘en0 · 192.168.x.x열림utun · VPN닫힘와이파이로 온 것만0.0.0.0:8080lo0 · 127.0.0.1열림en0 · 192.168.x.x열림utun · VPN열림전부 받는다
0.0.0.0 은 주소가 아니라 모든 문을 연다는 설정이다 — 컨테이너 안 서버는 0.0.0.0 으로 듣는다

0.0.0.0 의 정체: 설정값

  • 0.0.0.0 은 “모든 통로” 라는 뜻의 설정값이다
  • 실제 주소가 아니라서, 접속하는 쪽이 0.0.0.0 으로 찾아갈 수는 없다

쿠버네티스에서 자주 나는 문제

  • 컨테이너 안 서버를 127.0.0.1 로 띄우면 Pod 바깥에서 들어오는 연결을 받지 않는다
  • 결과는 connection refused
  • 컨테이너 안 서버는 0.0.0.0 으로 띄운다

3.2 HTTP 메시지 — 첫 줄이 용건이다

curl 로 보내는 요청

  • curl 로 요청을 보내 보자
  • -v 를 붙이면 주고받는 내용이 다 보인다
curl -v http://127.0.0.1:8080/
> GET / HTTP/1.1                 ← 보낸 것: 요청 줄
> Host: 127.0.0.1:8080
>
< HTTP/1.1 200 OK                ← 받은 것: 상태 줄
< Content-Length: 5
<
Hello                            ← 본문

볼 것은 첫 줄 두 개뿐

  • GET / HTTP/1.1 — / 경로를 GET 으로 달라는 요청, 끝은 HTTP 버전
  • HTTP/1.1 200 OK — 성공했다는 응답, 200 이 상태 코드다

상태 코드: 앞자리만 본다

  • 2xx 성공
  • 3xx 다른 곳으로 이동
  • 4xx 요청 쪽 문제
  • 5xx 서버 쪽 문제
  • 404 가 왔다면 네트워크는 정상이다 → 서버가 HTTP 로 답할 만큼은 닿았다는 뜻

4. TCP — 인사하고 꼼꼼하게

TCP 의 일: 빠짐없이, 순서대로

  • TCP 가 하는 일은 하나로 요약된다 — 데이터를 빠짐없이, 순서대로 전달하는 것
  • 그걸 위해 다섯 가지를 한다
연결
보내기 전에 양쪽이 연결부터 맺는다
신뢰성
빠진 건 다시 보내고 순서를 맞춘다
흐름 제어
받는 쪽이 감당할 만큼만 보낸다
혼잡 제어
네트워크가 막히면 천천히 보낸다
다중화
포트로 연결끼리 섞이지 않게 한다

흐름 제어와 혼잡 제어 구분법

  • 흐름 제어: 받는 쪽을 보호한다
  • 혼잡 제어: 중간 길을 보호한다

4.1 포트 — 창구 번호와 임시 번호표

포트의 역할

  • 한 컴퓨터에서 여러 프로그램이 동시에 통신할 수 있는 건 포트 덕분이다
  • 포트는 컴퓨터 안의 창구 번호다

서버 포트와 임시 포트

  • 서버는 정해진 번호에서 기다린다 — 우리 서버는 8080 이다
  • 반대로 접속하는 쪽은 OS 가 그때그때 남는 번호를 골라 준다
    • 이걸 임시(ephemeral) 포트라고 한다
Go 서버:8080 · 정해 둔 창구curl:58676탭 2:53125탭 3:53126출발 포트는 OS 가 그때그때 고른 임시 번호 · 도착 포트는 서버가 정해 둔 8080
같은 서버에 세 번 붙어도 출발 포트가 달라서 연결이 섞이지 않는다

결과: 탭마다 다른 출발 포트

  • 브라우저 탭 세 개로 같은 서버에 붙어도 섞이지 않는 이유가 이것이다
  • 탭마다 출발 포트가 다르다

4.2 seq 와 ack — 쪽 번호와 다음 쪽 요청

빠진 데이터를 찾는 번호 두 개

  • TCP 가 빠진 걸 알아채는 방법은 번호다 — 책에 쪽 번호를 매기는 것과 같다
  • seq: 지금 보내는 데이터가 몇 번째 바이트부터인지
  • ack: 어디까지 받았고, 다음엔 몇 번째부터 달라는지
17778curl 이 보낸 GET 요청 · seq 1 ~ 77 (77바이트)다음 →서버: ack 7877번까지 받았다, 다음은 78번부터
seq 는 몇 번째 바이트부터 보내는지, ack 는 다음에 몇 번째부터 달라는지

빠졌을 때: 재전송

  • 중간에 한 덩어리가 빠지면 ack 숫자가 올라가지 않는다
  • 보내는 쪽은 그걸 보고 다시 보낸다

TCP 헤더의 실제 모양

  • 한 줄이 32비트(4바이트)다
  • 색이 있는 칸만 알면 된다
01631 bit출발 포트도착 포트Sequence Number (seq)Acknowledgment Number (ack)길이FlagsWindowChecksumUrgent PointerOptions (MSS 등, 길이 가변)창구 번호 · 58676 → 8080몇 번째 바이트부터 보내나어디까지 받았나, 다음은 몇 번째SYN 시작 · ACK 확인FIN 정상 종료 · RST 강제 종료Window: 더 받을 수 있는 양점선 칸은 처음엔 몰라도 된다기본 20바이트 (Options 포함 최대 60) · 이 아래로 HTTP 데이터가 붙는다
색이 있는 칸 네 종류 — 포트, 번호(seq·ack), 신호(Flags), 받을 수 있는 양(Window) — 만 알면 TCP 캡처가 읽힌다

실무에서 자주 보는 RST

  • 아무도 기다리지 않는 포트에 연결을 시도하면 RST 가 돌아온다
  • 화면에는 connection refused 가 뜬다

4.3 3-Way Handshake — 세 번이어야 양쪽이 확인된다

데이터보다 연결이 먼저

  • TCP 는 데이터를 보내기 전에 연결부터 맺는다
  • 패킷 세 개를 주고받는다
클라이언트서버SYN · 연결 요청 (seq = x)SYN + ACK · 수락 + 반대 방향 요청 (ack = x+1, seq = y)ACK · 수락 확인 (ack = y+1)ESTABLISHED — 이제 데이터를 보낸다
세 번째 ACK 까지 와야 양쪽 모두 자기 신호가 상대에게 닿았음을 안다

두 번이면 남는 빈틈

  • 두 번이면 서버는 자기가 보낸 SYN+ACK 이 클라이언트에 도착했는지 알 수 없다
  • 세 번째 ACK 를 받아야 양쪽 모두 상대에게 닿았다는 걸 확인한다

함께 오가는 시작 번호

  • 이 과정에서 서로 시작 번호(seq)도 알려 준다
  • 시작 번호는 0 이 아니라 무작위다

4.4 연결 상태 — CLOSE_WAIT 가 쌓이면 내 코드다

연결 상태: 열한 가지 중 넷

  • 연결에는 상태가 있다
  • 열한 가지가 있지만 실무에서 보는 건 넷이다
  • 서버와 클라이언트가 각자 어떤 상태를 지나가는지 보면 이렇다
서버curlLISTENESTABLISHEDCLOSE_WAITCLOSEDESTABLISHEDFIN_WAITTIME_WAITSYNFIN 받음close()FIN 보냄FIN 받음LISTEN 이 없으면 서버가 안 떠 있다 · 127.0.0.1 로 듣고 있지는 않은지 (3.1)CLOSE_WAIT 에서 멈춰 쌓이면 내 앱이 close() 를 빠뜨린 것TIME_WAIT 는 먼저 끊은 쪽에 잠시 남는 정상 상태 · 너무 많으면 짧은 연결 과다
연결을 먼저 끊은 쪽은 TIME_WAIT 로, 끊김을 당한 쪽은 CLOSE_WAIT 를 거쳐 닫힌다

쌓인 상태로 읽는 원인

  • CLOSE_WAIT 가 수백, 수천 개씩 쌓이면 거의 확실히 코드 버그다
    • 상대는 끊었는데 내 앱이 close() 를 안 부르고 있다는 뜻이다
  • TIME_WAIT 는 먼저 끊은 쪽에 잠시 남는 정상 상태다
    • 너무 많으면 임시 포트가 모자라질 수 있다
ss -tanp            # Linux: 모든 연결과 상태
netstat -an -p tcp  # macOS

4.5 캡처한 12개 — 내용이 실린 건 두 개

직접 캡처하는 순서

  1. 서버를 띄운다
  2. 다른 창에서 tcpdump 를 켠다
  3. curl 을 한 번 보낸다
sudo tcpdump -i lo0 -nn tcp port 8080 -w ch01.pcap

옵션은 두 개만

  • -i lo0 — 어느 통로를 볼지
    • curl 과 서버가 같은 노트북에 있으니 자기 자신 통로(루프백)를 본다
  • tcp port 8080 — 무엇만 볼지, 8080 이 낀 TCP 패킷만

주의: 통로를 잘못 고르면 빈 캡처

  • 통로를 잘못 고르면 아무것도 안 찍힌다
  • 127.0.0.1 로 보낸 요청은 와이파이(en0)를 지나가지 않는다

캡처 결과

curl:58676Go server:8080연결3-wayHTTP내용종료FIN1 [S] seq 1229125408 · 연결 요청2 [S.] ack 1229125409 (x+1) · 수락 + 연결 요청3 [.] ack 1 · 확인 (tcpdump 상대 번호)4 [.] window update · macOS 루프백에서만5 [P.] GET / HTTP/1.1 — 77B6 [.] ack 787 [P.] HTTP/1.1 200 OK + Hello — 121B8 [.] ack 1229 [F.] curl 송신 종료 → FIN-WAIT10 [.] ack 79 → 서버 CLOSE-WAIT11 [F.] 서버 송신 종료 → LAST-ACK12 [.] ack 123 → curl TIME-WAIT
curl 한 번에 12개 — 데이터가 실린 건 주황 두 개뿐이고 나머지 열 개는 인사·확인·작별이다

12개 중 내용이 실린 두 개

  • curl 한 번에 패킷이 12개 오갔다
  • 그중 실제 내용이 실린 건 주황색 두 개, 요청(5번)과 응답(7번)뿐이다
  • 나머지 열 개의 쓰임
    • 연결을 맺는다
    • 받았다고 확인한다
    • 연결을 끊는다
  • TCP 가 꼼꼼하다는 게 이런 뜻이다

9~12번: 연결 끊기

  • 끝의 9~12번은 연결을 끊는 과정이다
  • 먼저 끊은 쪽이 curl 이다 → 마지막에 TIME-WAIT 로 잠시 남는 쪽도 curl 이다
  • 4.4 의 그림 그대로다

4.6 상자 뜯어보기 — 133바이트의 네 겹

요약 대신 바이트 원본

  • 방금 화면에 찍힌 한 줄은 tcpdump 가 고른 요약이다
  • -w 로 저장한 pcap 파일에는 바이트 원본이 그대로 들어 있다 → 요약에 없는 칸까지 다시 볼 수 있다
  • 요청이 실린 5번 패킷을 tshark 로 펼쳐 보자
tshark -r ch01.pcap -Y "frame.number==5" -V
5번 패킷 · 133바이트 = 4 + 20 + 32 + 77Loopback4BIP 헤더20BTCP 헤더32BHTTP77BFamily: IPEthernet 없음MAC 없음Version 4 · IHL 5TTL 64 · Protocol 6DF · Checksum 0x0000127.0.0.1 → 127.0.0.158676 → 8080seq 1 (raw 881046274)PSH, ACK · win 6380GET / HTTP/1.1Host: 127.0.0.1:8080User-Agent · Accepthex02 00 00 0045 … 40 06 … 7f000001e5 34 1f 90 …47 45 54 20 2f …Family = IPIPv4·20B / TTL 64 / TCP / 127.0.0.158676 / 8080"GET /"
패킷 안에 세그먼트, 세그먼트 안에 HTTP — 루프백이라 맨 바깥에 Ethernet 대신 4바이트 표시만 붙었다

캡슐화의 실물과 평문 GET

  • 133바이트가 네 겹으로 되어 있다 — 2.1 에서 그린 캡슐화가 실물로 이렇게 생겼다
  • 그림 아래 hex 줄에는 몇 바이트의 뜻을 적어 두었다
  • 마지막 47 45 54 가 중요하다
    • 글자 “GET” 그대로다
    • HTTP 는 암호화되지 않은 평문이라 글자가 그대로 보인다
  • 5장에서 같은 자리를 TLS 로 잠가서 다시 본다

맨 바깥에 없는 Ethernet

  • lo0 은 랜카드를 거치지 않는 가상 통로다 → MAC 주소 대신 4바이트짜리 표시만 붙는다
  • 진짜 Ethernet 과 MAC 은 01-2 에서 와이파이로 본다

4.7 tcpdump 의 함정 — 상대 번호와 체크섬

처음 보면 헷갈리는 것 ①: seq 가 갑자기 1

  • 1번 줄에서 1229125408 이던 번호가 3번 줄부터 1 로 바뀐다
  • 실제로 바뀐 게 아니다 → tcpdump 가 읽기 쉽게 시작 번호를 빼고 보여 주는 것이다
  • 원래 숫자를 보려면 -S 를 붙인다

처음 보면 헷갈리는 것 ②: 체크섬 경고

  • cksum incorrect 가 뜬다
  • 체크섬은 패킷이 깨졌는지 검사하는 값이다
  • tcpdump 는 찍은 바이트로 이 값을 직접 다시 계산해서 헤더 값과 비교한다
  • 그런데 요즘은 체크섬을 랜카드가 내보내기 직전에 채운다
    • tcpdump 는 그보다 먼저 찍는다
  • 그래서 내가 보낸 패킷에 뜨는 incorrect 는 대부분 무시해도 된다

5. TLS — 같은 요청을 잠그면

5.1 세 가지 약속 — 기밀성, 무결성, 인증

평문의 위험과 TLS

  • 4.6 에서 “GET” 이 글자 그대로 보였다
  • 같은 와이파이에 있는 누군가가 엿보면 다 읽힌다는 뜻이다
  • TLS 는 이걸 막는다 — HTTPS 의 S 가 TLS 다
기밀성
남이 내용을 읽지 못하게 암호화한다
무결성
중간에 내용이 바뀌면 알아챈다
인증
인증서로 상대가 진짜 그 서버인지 확인한다

TLS 가 잠그는 범위

  • TLS 는 HTTP 만 잠근다
  • IP 주소와 TCP 포트는 여전히 보인다
  • 그래서 HTTPS 에서도 tcpdump 는 쓸모 있다 → 연결이 됐는지, 어디서 끊겼는지는 그대로 보인다

5.2 1.3 과 1.2 — 왕복 하나와 인증서 하나

비교 실험: 책의 1.2 와 지금의 1.3

  • 책이 설명하는 TLS 연결 과정은 TLS 1.2 시절 방식이다
  • 지금은 TLS 1.3 이 기본이다
  • 무엇이 달라졌는지 직접 비교해 봤다
    • 8443 포트에 HTTPS 서버를 띄웠다
    • 같은 요청을 1.3 과 1.2 로 한 번씩 보냈다
C=/opt/homebrew/opt/curl/bin/curl   # macOS 기본 curl 은 TLS 1.3 이 안 된다
$C -sk --http1.1 --tlsv1.3 https://127.0.0.1:8443/
$C -sk --http1.1 --tlsv1.2 --tls-max 1.2 https://127.0.0.1:8443/
TLS 1.3 · 연결 58734TLS 1.2 · 연결 58735curlservercurlserverClientHello + key_share (1522B)ServerHello + 🔒 인증서 · Finished🔒 Finished + GET /🔒 200 OK HelloClientHello (260B)ServerHello · 인증서(평문) · DoneClientKeyExchange · FinishedFinished🔒 GET /🔒 200 OK Hello1 왕복 뒤 GET · X25519MLKEM7682 왕복 뒤 GET · ECDHE x25519
초록은 열쇠 없이는 못 읽는 구간 — 1.3 은 한 왕복 빠르고 인증서까지 잠근다

차이 세 가지

  1. 1.3 이 한 왕복 빠르다
    • 1.3 은 첫 인사에 암호 열쇠를 만들 재료를 미리 실어 보낸다
    • 그래서 서버 답장을 받자마자 바로 GET 을 보낸다
    • 1.2 는 한 번 더 주고받은 뒤에야 보낸다
  2. 1.3 은 인증서까지 숨긴다
    • 1.2 는 서버 인증서가 평문으로 보였고, 1.3 은 보이지 않았다
  3. 1.3 은 열쇠를 네트워크로 보내지 않는다
    • 양쪽이 재료를 하나씩 내놓고 각자 같은 열쇠를 계산한다
    • 그래서 나중에 서버 열쇠가 유출돼도 지난 통신은 풀리지 않는다

캡처에서 본, 책에 없는 것

  • 1.3 의 열쇠 재료가 X25519MLKEM768 이었다
  • 양자 컴퓨터에도 견디도록 설계된 최신 방식이 이미 기본으로 쓰이고 있다

5.3 같은 자리의 다른 바이트 — 열쇠가 있으면 다시 GET

바이트로 확인: 열쇠 파일

  • 정말 잠겼는지 바이트로 확인해 보자
  • 실습 서버는 세션 열쇠를 파일(tls-keys.log)로 남기게 해 두었다
  • 이 파일을 tshark 에 주면 잠긴 내용을 풀 수 있다
tshark -r tls.pcap -Y tls                                   # 열쇠 없이
tshark -r tls.pcap -o tls.keylog_file:tls-keys.log -Y tls   # 열쇠로
평문 HTTPch01.pcap #5TLS 1.3tls.pcap #10열쇠로 복호화tls-keys.log47 45 54 20 2f 20 48 54 54 50→ "GET / HTTP"50 d5 b2 72 e4 7a a5 98 96 a3→ Application DataGET / HTTP/1.1→ 200 OK · Hello
같은 GET 이 잠기면 의미 없는 바이트가 되고, 세션 키가 있으면 다시 글자가 된다

결과: 잠그면 의미 없는 바이트

  • 평문일 때 “GET” 이 보이던 자리가, 잠그면 의미 없는 바이트가 된다
  • 열쇠를 넣으면 다시 GET / HTTP/1.1 이 나온다
  • 이 열쇠 파일은 통신을 푸는 열쇠 그 자체다 → 실습이 아니면 켜 두지 않는다

6. UDP — 인사 없이 던지고 끝

TCP 의 비용과 UDP

  • TCP 의 꼼꼼함에는 비용이 든다 — 4.5 의 12개 중 열 개가 그 비용이었다
  • UDP 는 그 비용을 아예 내지 않는다
  • 연결도, 확인도, 재전송도 없이 데이터를 보내고 끝이다

UDP 를 쓰는 곳

  • 짧게 묻고 짧게 답하면 끝나는 DNS
  • 늦게 오느니 조금 끊기는 게 나은 인터넷 전화와 영상

6.1 8바이트 헤더 — seq 도 ack 도 없다

헤더 크기 비교

  • UDP 헤더는 칸이 네 개, 8바이트가 전부다
  • TCP 헤더와 같은 축척으로 그리면 차이가 바로 보인다
01631 bit출발 포트도착 포트길이체크섬UDP 헤더 8바이트가 전부 — seq · ack · Flags · Window 칸이 없다20B8BTCPUDP
같은 축척으로 그리면 UDP 헤더는 TCP 기본 헤더의 2/5 다

번호가 없을 때 잃는 것

  • seq 도 ack 도 없다 → 몇 번째인지도 받았는지도 알 방법이 없다

6.2 열린 포트와 닫힌 포트 — 거절은 ICMP 가 한다

실험: 열린 9000 번과 빈 9001 번

  • 같은 Hello 서버를 UDP 9000 번으로 만들고, 두 번 보내 봤다
    • 한 번은 서버가 있는 9000 번
    • 한 번은 아무도 없는 9001 번
echo hi | nc -u -w1 127.0.0.1 9000   # Hello 가 돌아온다
echo hi | nc -u -w1 127.0.0.1 9001   # 아무것도 안 돌아온다
UDP → :9000 (서버 있음)UDP → :9001 (아무도 없음)ncserverncOS1 UDP "hi\n" · Len=32 UDP "Hello" · Len=53 UDP "hi\n" · Len=34 ICMP 3/3 Port unreachable패킷 2개로 끝 · TCP 였다면 12개인사도 확인도 작별도 없다UDP 에는 RST 가 없어 OS 가 ICMP 로 거절nc 는 거절을 모른 채 exit 01번 패킷 35B = Loopback 4 + IP 20 + UDP 8 + "hi\n" 3 · UDP Length 11 = 8 + 3
보내고 끝 — 닫힌 포트의 거절조차 UDP 가 아니라 ICMP 가 대신 전한다

왼쪽: 패킷 두 개로 끝

  • 보내고, 답이 오고, 끝
  • TCP 였다면 12개였다

오른쪽: OS 가 대신 보내는 ICMP 거절

  • UDP 에는 TCP 의 RST 같은 거절 신호가 없다
  • 그래서 OS 가 대신 ICMP 라는 별도 메시지로 “그 포트는 없다” 를 돌려준다
  • 그런데 nc 는 그걸 모른 채 정상 종료했다
  • UDP 는 상대가 받았는지 모른다는 말이 이런 뜻이다

6.3 QUIC — UDP 위에 다시 쌓은 꼼꼼함

QUIC 의 출발점

  • UDP 에 신뢰성이 없다는 건 UDP 자체 이야기다
  • UDP 위에 꼼꼼함을 다시 쌓으면 된다
  • 그게 QUIC 이고, HTTP/3 이 QUIC 을 쓴다
HTTP/2HTTP/3HTTPTLSTCP재전송 · 혼잡 제어IPHTTPQUICTLS 1.3 + 재전송 + 혼잡 제어UDP운반만IP인사를 TCP 한 번, TLS 한 번연결과 암호화 준비를 한 번에
QUIC 은 TCP 가 하던 일과 TLS 를 UDP 위에서 다시 한다

QUIC 이 다시 넣은 것과 효과

  • TCP 가 하던 재전송과 혼잡 제어를 다시 구현했다
  • TLS 1.3 까지 안에 넣었다
  • 덕분에
    • 연결과 암호화 준비가 한 번에 끝난다
    • 와이파이에서 LTE 로 바뀌어도 연결이 끊기지 않는다

정리: TCP, UDP, QUIC 의 관계

  • TCP: 연결하고 꼼꼼하게 보낸다
  • UDP: 연결 없이 보내고 끝이다
  • QUIC: UDP 위에 TCP 의 꼼꼼함과 TLS 를 다시 얹은 것이다

7. Discussion

핵심 메시지

층
외울 목록이 아니라 상자 몇 겹. 고장 위치를 찾는 지도
TCP
연결하고, 번호로 확인하고, 끊는다. 12개 중 내용은 2개
TLS
1.3 은 한 왕복 빠르고 인증서까지 잠근다
UDP
8바이트로 보내고 끝. 거절은 ICMP 가, 꼼꼼함은 QUIC 이
tcpdump
통로(-i)와 필터를 고른다. 상대 번호와 체크섬 경고에 속지 않는다

컴퓨터 한 대 안에서 curl 한 번을 끝까지 따라가 보면, 층은 외울 목록이 아니라 상자 몇 겹이다. 그 상자를 tcpdump 로 직접 뜯어 본 경험이 있으면, 캡처 화면이 더 이상 암호처럼 보이지 않는다. 다음 편은 이 상자가 컴퓨터 밖으로 나가 라우터를 건너고, 쿠버네티스 노드 사이를 오가는 이야기다.


8. References

TLS 1.3 이 무엇을 막으려는 프로토콜인지는 RFC 8446 의 Abstract 첫 문단에 적혀 있다.
"TLS allows client/server applications to communicate over the Internet in a way that is designed to prevent eavesdropping, tampering, and message forgery."
[해석] "TLS 는 클라이언트/서버 애플리케이션이 도청, 변조, 메시지 위조를 막도록 설계된 방식으로 인터넷에서 통신하게 한다." — 5.1 의 세 가지 약속(기밀성·무결성·인증)의 근거다. TLS 1.0·1.1 의 폐지는 RFC 8996 에 있다.
→ RFC Editor — RFC 8446 The Transport Layer Security (TLS) Protocol Version 1.3
→ RFC Editor — RFC 8996 Deprecating TLS 1.0 and TLS 1.1
QUIC 이 UDP 위에서 무엇을 다시 제공하는지는 RFC 9000 의 Abstract 에 요약되어 있다.
"QUIC provides applications with flow-controlled streams for structured communication, low-latency connection establishment, and network path migration."
[해석] "QUIC 은 애플리케이션에 구조화된 통신을 위한 흐름 제어 스트림, 낮은 지연의 연결 수립, 네트워크 경로 이동을 제공한다." — 6.3 에서 말한 독립 스트림, 한 번에 끝나는 인사, 와이파이에서 LTE 로 바뀌어도 유지되는 연결의 근거다. HTTP/3 은 RFC 9114, HTTP 의 의미는 RFC 9110 이다.
→ RFC Editor — RFC 9000 QUIC: A UDP-Based Multiplexed and Secure Transport
→ RFC Editor — RFC 9110 HTTP Semantics