Network: HTTP/3, TLS và WebSocket
5. HTTP/3 — QUIC over UDP
RFC 9114 (2022). HTTP/3 giải quyết vấn đề cuối cùng còn sót lại của HTTP/2: TCP-level HOL blocking.
5.1 Vấn đề HTTP/2 chưa giải quyết được
HTTP/2 multiplex nhiều streams trên 1 TCP connection — nhưng TCP không biết đến khái niệm streams. Với TCP, đây chỉ là một luồng bytes. Khi một packet bị mất, TCP yêu cầu retransmit trước khi tiếp tục — và điều này block tất cả streams, dù chỉ một stream bị ảnh hưởng thật sự.
Stream 1: [frame1][frame2] [frame4] ← chờ frame3
Stream 3: [frame1] [frame2] ← chờ frame3
Stream 5: [frame1][frame2][frame3][frame4] ← chờ frame3
↑
Packet loss tại đây
→ TẤT CẢ streams phải chờ TCP retransmit
5.2 QUIC Protocol
QUIC được Google phát triển khoảng 2012, chuẩn hóa bởi IETF năm 2021 (RFC 9000). Thay vì cố gắng vá TCP, Google chọn build trên UDP — một giao thức đơn giản, không có logic reliability — rồi implement mọi thứ cần thiết ở application layer, nhưng tốt hơn TCP.
HTTP/1.1, HTTP/2: HTTP/3:
┌─────────────┐ ┌─────────────┐
│ HTTP │ │ HTTP/3 │
├─────────────┤ ├─────────────┤
│ TLS 1.3 │ │ QUIC │ ← TLS 1.3 tích hợp bên trong
├─────────────┤ ├─────────────┤
│ TCP │ │ UDP │
├─────────────┤ ├─────────────┤
│ IP │ │ IP │
└─────────────┘ └─────────────┘
5.3 QUIC Packet Format
QUIC Long Header (handshake):
┌─┬────────────────────────────────────────────────┐
│1│ Header Form (1) + Version (32) + ... │
├─┴────────────────────────────────────────────────┤
│ Destination Connection ID │
│ Source Connection ID │
│ Packet-type-specific fields... │
└──────────────────────────────────────────────────┘
QUIC Short Header (data):
┌─┬──────────────────────────────────────────────┐
│0│ Fixed Bit + Spin + Key Phase + Packet Number │
├─┴──────────────────────────────────────────────┤
│ Destination Connection ID │
│ Packet Number (1-4 bytes) │
│ Payload (QUIC frames, encrypted) │
└────────────────────────────────────────────────┘
5.4 Connection ID — Tính năng độc đáo
TCP định danh connection bằng bộ tứ (src IP, src port, dst IP, dst port). Khi bạn chuyển từ WiFi sang 4G, IP thay đổi → TCP connection bị hủy → phải reconnect lại từ đầu.
QUIC dùng Connection ID (64-bit random) để định danh connection. IP có thể đổi thoải mái, connection vẫn sống.
TCP: (192.168.1.5:50000, 93.184.216.34:443) → IP đổi → chết
QUIC: ConnectionID=0xA3F2... → IP đổi → vẫn sống
Thực tế: Bạn xem YouTube, đi vào thang máy, WiFi mất, 4G bắt
HTTP/2: buffer, reconnect, có thể giật
HTTP/3: seamless, không gián đoạn
5.5 0-RTT Connection Establishment
HTTP/2 (TCP + TLS 1.3):
RTT 1: TCP SYN / SYN-ACK
RTT 2: TLS ClientHello / ServerHello
RTT 3: First HTTP request
→ Tổng: 2-3 RTT trước khi nhận data
HTTP/3 (QUIC):
First connection:
RTT 1: QUIC Initial (kết hợp TCP+TLS handshake)
RTT 2: First HTTP request
→ Tổng: 1 RTT
Resumed connection (0-RTT):
→ Gửi request NGAY trong packet đầu tiên — 0 RTT overhead!
→ Dùng Pre-Shared Key từ session trước
⚠️ 0-RTT có thể bị replay attack. Chỉ dùng cho idempotent requests (GET), không dùng cho POST.
5.6 QUIC Streams — Giải quyết HOL Blocking hoàn toàn
Khác với TCP, mỗi QUIC stream có packet numbering độc lập. Mất packet của stream 1 chỉ ảnh hưởng đến stream 1.
Stream 1: packet 1, 2, [mất 3], 4... → chỉ Stream 1 chờ retransmit
Stream 3: packet 1, 2, 3, 4... → tiếp tục bình thường
Stream 5: packet 1, 2, 3, 4... → tiếp tục bình thường
5.7 Tại sao QUIC dùng UDP thay vì cải tiến TCP?
TCP đã ăn sâu vào OS kernel và toàn bộ infrastructure (firewall, NAT, router). Mọi middlebox đều hiểu và có thể xử lý TCP. Thay đổi TCP protocol mà không break hạ tầng hiện có là gần như bất khả thi — gọi là "ossification".
QUIC chạy trên UDP tránh được vấn đề này: chạy ở user space, update được như một thư viện thông thường. QUIC tự implement reliability, ordering, congestion control — không bị ràng buộc bởi backward compatibility của TCP.
5.8 QPACK — Header Compression cho HTTP/3
HTTP/2 dùng HPACK, nhưng HPACK yêu cầu headers được xử lý đúng thứ tự — vì dynamic table được cập nhật tuần tự. Với QUIC, các streams hoạt động độc lập và có thể arrive out-of-order → nếu dùng HPACK thẳng sẽ deadlock.
HTTP/3 dùng QPACK (RFC 9204): tách thành 2 unidirectional streams đặc biệt — một stream gửi dynamic table updates, một stream gửi acknowledgments. Headers không cần đúng thứ tự nhưng vẫn decode được đúng.
6. TLS/HTTPS — Bảo mật tầng transport
6.1 TLS 1.3 Handshake (RFC 8446)
TLS 1.3 tinh giản đáng kể so với 1.2: loại bỏ các cipher yếu, bắt buộc ECDH key exchange, và mã hóa phần lớn handshake ngay từ đầu.
Client Server
│ │
│── ClientHello ───────────────────►│
│ - key_share (ECDH public key) │
│ - supported cipher suites │
│ │
│◄─ ServerHello ────────────────────│
│ - key_share (server ECDH key) │
│ - selected cipher suite │
│ │
│ [Cả hai bên derive Handshake Keys từ ECDH — không cần thêm round trip]
│ │
│◄─ {EncryptedExtensions} ──────────│ ← Từ đây mọi thứ đều encrypted
│◄─ {Certificate} ──────────────────│
│◄─ {CertificateVerify} ────────────│
│◄─ {Finished} ─────────────────────│
│ │
│── {Finished} ────────────────────►│
│── {Application Data} ────────────►│ ← 1 RTT total!
6.2 TLS 1.2 vs TLS 1.3
| TLS 1.2 | TLS 1.3 | |
|---|---|---|
| Handshake RTT | 2 RTT | 1 RTT |
| 0-RTT resumption | Session tickets | 0-RTT PSK |
| Key exchange | RSA hoặc ECDH | Chỉ ECDH (PFS bắt buộc) |
| Cipher suites | Nhiều, có weak | Chỉ 5 strong suites |
| Handshake encryption | Không | Có (từ ServerHello) |
| Loại bỏ | RC4, MD5, SHA-1, CBC mode |
6.3 Perfect Forward Secrecy (PFS)
PFS là thuộc tính đảm bảo rằng ngay cả khi server bị compromise trong tương lai, attacker cũng không thể decrypt traffic đã bắt trước đó.
Không có PFS (RSA key exchange): server có một private key cố định. Nếu attacker ghi lại toàn bộ encrypted traffic hôm nay, sau này lấy được private key → decrypt tất cả.
Có PFS (ECDH ephemeral): mỗi session tạo ra một ephemeral key pair mới. Session key không bao giờ đi qua mạng. Private key bị xóa sau session — compromise về sau không cứu được gì.
6.4 Certificate Chain
Trình duyệt không tin tưởng mọi certificate — chỉ tin các Root CA đã được cài sẵn trong OS/browser. Certificate của website phải có chain of trust về đến một Root CA đó.
Root CA (tự ký, trong browser/OS trust store)
│ ký
▼
Intermediate CA
│ ký
▼
End-entity Certificate (example.com)
└── Subject: CN=example.com
Valid: 2024-01-01 to 2025-01-01
SAN: example.com, *.example.com
Signature: [signed by Intermediate CA]
Trình duyệt verify:
1. Certificate chưa hết hạn
2. Signature chain về đến Root CA tin cậy
3. Domain match (CN hoặc SAN)
4. Certificate chưa bị revoke (CRL/OCSP)
6.5 ALPN — Application-Layer Protocol Negotiation
ALPN cho phép client và server thỏa thuận giao thức (HTTP/1.1 hay HTTP/2) ngay trong TLS handshake, không tốn thêm round trip.
Client gửi trong TLS ClientHello:
Extension: ALPN
Protocols: ["h2", "http/1.1"] ← thứ tự = ưu tiên
Server chọn và thông báo trong ServerHello:
Extension: ALPN
Protocol: "h2"
7. WebSocket
7.1 Tại sao cần WebSocket?
HTTP là giao thức request-response: client hỏi, server trả lời. Server không thể chủ động gửi data xuống client.
Trước WebSocket, dev phải dùng các trick:
- Polling: client hỏi mỗi N giây — đơn giản nhưng lãng phí và có latency lên đến N giây.
- Long Polling: client gửi request, server giữ kết nối cho đến khi có data mới mới trả lời — latency thấp hơn, nhưng cần liên tục tạo kết nối mới sau mỗi response.
WebSocket giải quyết triệt để: sau khi handshake, connection trở thành full-duplex — cả hai bên có thể gửi data bất cứ lúc nào với latency ~1ms.
7.2 WebSocket Handshake (HTTP Upgrade)
WebSocket bắt đầu bằng HTTP request thông thường rồi "upgrade" connection:
Client:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Server:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
↑
base64(SHA1(key + magic GUID))
Ngăn non-WebSocket server phản hồi nhầm
Sau đó TCP connection này không còn là HTTP nữa — chỉ WebSocket frames.
7.3 WebSocket Frame Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌─┬─┬─┬─┬─────────┬─┬───────────────────────────────────────────┐
│F│R│R│R│ Opcode │M│ Payload Length │
│I│S│S│S│ (4) │A│ (7 bits or 7+16 or 7+64) │
│N│V│V│V│ │S│ │
│ │1│2│3│ │K│ │
├─┴─┴─┴─┴─────────┴─┴───────────────────────────────────────────┤
│ Masking-key (32 bits, only if MASK=1) │
├───────────────────────────────────────────────────────────────┤
│ Payload Data │
└───────────────────────────────────────────────────────────────┘
Opcodes:
0x1 = text frame (UTF-8)
0x2 = binary frame
0x8 = close
0x9 = ping 0xA = pong
Client → Server PHẢI mask payload. Server → Client không mask.
Lý do: ngăn cache poisoning attacks trên proxy.
7.4 WebSocket vs SSE vs Long Polling
| Long Polling | SSE | WebSocket | |
|---|---|---|---|
| Direction | Full duplex | Server→Client only | Full duplex |
| Protocol | HTTP | HTTP | WS (upgrade từ HTTP) |
| Complexity | Thấp | Thấp | Trung bình |
| Auto-reconnect | Manual | Built-in | Manual |
| Binary support | Có | Không (text only) | Có |
| Use case | Notifications | Live feed, stocks | Chat, game, collab |
| HTTP/2 friendly | Có | Tốt hơn (streams) | Cần riêng |