Bốn bài trước ở bên trong một chiếc máy: bit và phép cộng, CPU và cache, bộ nhớ, hệ điều hành. Bài cuối đi ra ngoài, theo đúng một việc bạn vừa làm để đọc bài này: gõ địa chỉ một trang web và chờ nó hiện ra. Trên một kết nối mới, việc đó mất khoảng nửa giây, và trong nửa giây ấy, yêu cầu của bạn đi qua năm lớp của mạng máy tính, hai lần: xuống ở máy bạn, lên ở máy chủ, rồi ngược lại.
Lộ trình: mô hình 5 lớp và 7 lớp OSI, toàn cảnh một lần mở trang theo thời gian, rồi từng lớp từ trên xuống (ứng dụng với DNS, TLS, HTTP; giao vận với TCP; mạng với IP; liên kết dữ liệu; vật lý), một gói tin được bọc ra sao, phía sau Cloudflare, và những bài học thực tế.
Mọi con số trong bài đều đo trên máy thật, tới chính viet.sarl, bằng dig, ping, curl, openssl, traceroute và Python 3.12. Những gì liên quan tới mạng của người đo (địa chỉ IP, nhà mạng, các chặng trên đường đi) và địa chỉ của máy chủ gốc đều được cố ý bỏ ra; chỉ những gì ai cũng thấy được từ bên ngoài mới được in.
Năm lớp, và bảy lớp OSI
Mạng máy tính được chia thành các lớp, mỗi lớp lo một việc và chỉ nói chuyện với lớp ngay trên và ngay dưới nó. Lớp trên không cần biết lớp dưới làm thế nào; nó chỉ đưa dữ liệu xuống và tin rằng dữ liệu sẽ tới.
Có hai cách chia hay gặp. Mô hình OSI có 7 lớp, là mô hình chuẩn trong sách vở và tài liệu. Mô hình 5 lớp gộp ba lớp trên cùng của OSI làm một, và sát với cách Internet thật sự chạy (bộ giao thức TCP/IP). Bài này đi theo 5 lớp; bảng dưới để bạn gặp tên lớp nào ở đâu cũng nhận ra:
| Mô hình 5 lớp | Lớp OSI tương ứng | Đơn vị dữ liệu | Trong lần mở viet.sarl |
|---|---|---|---|
| 5. Ứng dụng | 7 Ứng dụng, 6 Trình diễn, 5 Phiên | Thông điệp | DNS, TLS, HTTP/2 |
| 4. Giao vận | 4 Giao vận | Đoạn (segment) | TCP, cổng 443 |
| 3. Mạng | 3 Mạng | Gói (packet) | IPv4 hoặc IPv6, định tuyến qua 13 chặng |
| 2. Liên kết dữ liệu | 2 Liên kết dữ liệu | Khung (frame) | Wi-Fi hoặc Ethernet, địa chỉ MAC |
| 1. Vật lý | 1 Vật lý | Bit | Sóng radio, tín hiệu điện, ánh sáng trong cáp quang |
(TLS hay được xếp vào lớp "trình diễn" hoặc "phiên" của OSI, vì nó mã hóa dữ liệu và giữ một phiên an toàn; trong mô hình 5 lớp, nó nằm trong lớp ứng dụng, ngay trên TCP.)
Cách các lớp làm việc với nhau giống như gửi thư qua nhiều tầng phong bì: lớp ứng dụng viết lá thư (một yêu cầu HTTP), lớp giao vận bỏ nó vào phong bì có ghi số cổng, lớp mạng bỏ tiếp vào phong bì có ghi địa chỉ IP, lớp liên kết bỏ vào phong bì có ghi địa chỉ MAC của chặng kế tiếp. Ở đầu kia, từng lớp bóc đúng phong bì của mình. Việc bọc từng tầng này gọi là đóng gói (encapsulation).
Toàn cảnh: nửa giây chia ra thế nào
curl có thể báo thời điểm mỗi chặng xong. Mở https://viet.sarl/series năm lần, mỗi lần một kết nối mới:
curl -s -o /dev/null -w '%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n' https://viet.sarl/series
| Mốc (tính từ lúc bắt đầu) | Trung vị của 5 lần | Khoảng |
|---|---|---|
| Xong DNS (biết địa chỉ IP) | 2 ms | 2–60 ms |
| Xong bắt tay TCP | 68 ms | 51–122 ms |
| Xong bắt tay TLS | 145 ms | 123–222 ms |
| Nhận byte đầu tiên của trang | 587 ms | 336–1.956 ms |
| Nhận xong toàn bộ trang | 592 ms | 343–1.958 ms |
Để so sánh, một gói ping đi tới máy chủ gần nhất của Cloudflare và quay về mất 55–99 ms (trung bình 74 ms). Con số này gọi là RTT (round-trip time), và nó là đơn vị đo quan trọng nhất của mạng: TCP mất khoảng một RTT, TLS thêm khoảng một RTT nữa, và yêu cầu HTTP thêm ít nhất một RTT. Phần còn lại là thời gian máy chủ làm việc. Từng mốc trong bảng sẽ được giải thích ở các mục dưới.
Lớp 5, ứng dụng: DNS
Máy tính không biết viet.sarl ở đâu; nó cần một địa chỉ IP. DNS (Domain Name System) là cuốn danh bạ của Internet:
$ dig viet.sarl A
viet.sarl. 300 IN A 172.67.207.225
viet.sarl. 300 IN A 104.21.22.246
;; Query time: 232 msec
$ dig viet.sarl AAAA
viet.sarl. 300 IN AAAA 2606:4700:3034::6815:16f6
viet.sarl. 300 IN AAAA 2606:4700:3036::ac43:cfe1
- Tên này ứng với hai địa chỉ IPv4 (bản ghi
A) và hai địa chỉ IPv6 (bản ghiAAAA), đều thuộc Cloudflare, không phải máy chủ thật của trang web. Cloudflare đứng giữa, che địa chỉ máy chủ gốc và chặn bớt tấn công. - Số
300là TTL: kết quả được phép giữ lại 300 giây. Đó là lý do lần hỏi đầu tốn 232 ms nhưng các lần sau trong bảng ở trên chỉ tốn 2 ms: hệ điều hành đã nhớ câu trả lời. - Hỏi thẳng máy chủ DNS công cộng của Cloudflare (
dig @1.1.1.1) mất 93 ms.
Khi chưa ai nhớ câu trả lời, máy chủ DNS phải hỏi lần lượt từ gốc xuống: máy chủ gốc chỉ tới máy chủ quản lý đuôi .sarl, máy chủ đó chỉ tới máy chủ của tên miền (ở đây cũng là Cloudflare), và máy chủ đó mới có câu trả lời. Mỗi bước là một chuyến đi qua mạng, nên DNS chưa được nhớ có thể tốn hàng trăm mili giây trước khi việc thật sự bắt đầu.
Lớp 4, giao vận: TCP
Có địa chỉ IP rồi, máy bạn mở một kết nối TCP tới cổng 443 (cổng mặc định của HTTPS). Cổng (port) là con số giúp một máy phân biệt các ứng dụng: cùng một địa chỉ IP, cổng 443 là web, cổng 22 là SSH.
Mạng bên dưới không hứa gì cả: gói tin có thể mất, tới sai thứ tự, hay tới hai lần. TCP biến nó thành một dòng byte tin cậy và đúng thứ tự:
- Bắt tay 3 bước: máy bạn gửi
SYN, máy chủ trảSYN-ACK, máy bạn gửiACK(và có thể gửi dữ liệu luôn sau đó). Hai bên thống nhất số thứ tự bắt đầu. Đó là khoảng một RTT: trong bảng, TCP xong ở mốc 51–122 ms, khớp với RTT 55–99 ms củaping. - Số thứ tự và xác nhận: mỗi byte có một số thứ tự; bên nhận báo lại đã nhận tới đâu; gói nào không được xác nhận sau một thời gian thì bị gửi lại.
- Kiểm soát tắc nghẽn: TCP bắt đầu gửi chậm rồi tăng dần, và giảm tốc khi thấy mất gói, để không làm nghẽn mạng.
Mỗi đoạn TCP chở được bao nhiêu dữ liệu? Hỏi thẳng hệ điều hành:
import socket
s = socket.create_connection(("viet.sarl", 443))
print(s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)) # 1348
1.348 byte mỗi đoạn (MSS, maximum segment size). Theo lý thuyết, với khung Ethernet 1.500 byte, con số này phải là 1.500 − 20 (IP) − 20 (TCP) = 1.460. Nhỏ hơn thế nghĩa là ở đâu đó trên đường đi có một thiết bị yêu cầu gói nhỏ hơn, thường vì đường truyền đó phải bọc thêm một lớp nữa (đường hầm, VPN, hay một số loại kết nối của nhà mạng). Người dùng không thấy gì; TCP tự điều chỉnh.
Lớp 5, ứng dụng: TLS 1.3
Trên kết nối TCP, trước khi gửi bất cứ thứ gì có nghĩa, hai bên dựng một kênh mã hóa bằng TLS:
$ openssl s_client -connect viet.sarl:443 -servername viet.sarl -alpn h2,http/1.1
subject=CN=viet.sarl
issuer=C=US, O=Let's Encrypt, CN=YE2
Protocol: TLSv1.3
ALPN protocol: h2
Trong một lần bắt tay, TLS làm ba việc:
- Xác minh danh tính: máy chủ đưa ra một chứng chỉ ghi tên
viet.sarl, được ký bởi một tổ chức mà trình duyệt tin tưởng (ở đây là Let's Encrypt). Không có bước này, một kẻ đứng giữa có thể giả làm trang web. - Thống nhất khóa: hai bên tạo ra một khóa bí mật chung mà không ai nghe lén trên đường truyền tính ra được, rồi mã hóa mọi thứ sau đó bằng khóa đó (ở đây là AES-256-GCM).
- Chọn giao thức tầng trên (ALPN): hai bên thống nhất sẽ nói HTTP/2 (
h2).
TLS 1.3 làm tất cả trong một RTT (các bản cũ cần hai), khớp với bảng: TLS xong khoảng 70–110 ms sau TCP. Chi tiết -servername viet.sarl cũng đáng chú ý: tên trang web thường được gửi đi ở dạng chưa mã hóa, trước khi kênh mã hóa có (SNI; một phần mở rộng mới hơn tên là ECH mã hóa được cả nó), vì một địa chỉ IP của Cloudflare phục vụ hàng triệu trang và máy chủ cần biết nên đưa ra chứng chỉ nào.
Lớp 5, ứng dụng: HTTP
Giờ mới tới yêu cầu thật sự. Gửi tay một yêu cầu HTTP/1.1 qua kênh TLS bằng Python, để thấy nó chỉ là văn bản:
req = b"GET /series HTTP/1.1\r\nHost: viet.sarl\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n"
| Byte | |
|---|---|
| Yêu cầu | 83 |
| Phần header của câu trả lời | 815 |
| Nội dung trang, nén gzip | 6.609 |
| Cùng nội dung, không nén | 23.295 |
Toàn bộ "lá thư" chỉ là 83 byte chữ. Trang trả về được nén còn chưa tới một phần ba: nén văn bản trước khi gửi là cách rẻ nhất để tiết kiệm băng thông.
Trình duyệt thật dùng HTTP/2 (đã chọn ở bước ALPN): yêu cầu và câu trả lời được đóng thành các khung nhị phân, và nhiều yêu cầu (trang, CSS, script, ảnh) đi song song trên cùng một kết nối. Header của câu trả lời còn có dòng alt-svc: h3=":443": máy chủ báo rằng nó cũng nói HTTP/3, chạy trên QUIC qua UDP thay vì TCP, gộp luôn bắt tay giao vận và TLS vào một RTT. Lần sau, trình duyệt có thể dùng nó.
Vì sao byte đầu tiên đến chậm như vậy (trung vị 587 ms, có lần gần 2 giây)? Header trả lời có câu trả lời: cf-cache-status: DYNAMIC. Cloudflare không giữ sẵn trang này, nên mỗi yêu cầu đều phải đi tiếp từ Cloudflare về máy chủ gốc, chờ ứng dụng tạo trang, rồi quay lại. Phần chênh lệch lớn giữa các lần đo chính là chặng đó. Khi dùng lại một kết nối đã mở cho yêu cầu thứ hai và thứ ba, không còn bắt tay TCP và TLS, và byte đầu tiên đến sau khoảng 130 ms thay vì hơn nửa giây.
Lớp 3, mạng: IP và định tuyến
Mỗi gói tin mang một địa chỉ IP nguồn và đích. Không có máy nào biết toàn bộ đường đi; mỗi router chỉ biết, với một dải địa chỉ đích, nên chuyển gói sang hướng nào. Gói tin đi qua từng router như thư qua từng bưu cục. traceroute đếm được 13 chặng từ máy đo tới Cloudflare, chặng cuối có RTT khoảng 57 ms. (Các chặng ở giữa thuộc về mạng nhà và nhà mạng của người đo, nên không được in.)
Mỗi gói còn có một trường TTL (ở IPv6 gọi là hop limit): mỗi router giảm nó đi 1, về 0 thì gói bị bỏ và router báo lại cho người gửi. Đó là cách traceroute hoạt động: gửi gói với TTL = 1, 2, 3… để mỗi chặng lần lượt tự lộ diện. TTL tồn tại để một gói đi lạc vào vòng lặp không chạy vòng mãi.
Có một điểm thú vị ở các địa chỉ của Cloudflare: chúng là anycast. Cùng một địa chỉ IP được công bố từ hàng trăm trung tâm dữ liệu trên thế giới, và định tuyến tự đưa gói tin tới nơi gần nhất (theo đường mạng). Người ở Hà Nội và người ở Paris gõ cùng một địa chỉ nhưng nói chuyện với hai máy chủ khác nhau.
IPv4 chỉ có khoảng 4,3 tỉ địa chỉ, không đủ cho mọi thiết bị. Vì vậy máy trong mạng nhà thường dùng địa chỉ riêng (dạng 192.168.x.x), và router nhà bạn dịch địa chỉ (NAT) sang một địa chỉ công cộng duy nhất khi gói tin đi ra Internet. IPv6, với 128 bit địa chỉ, không cần mẹo đó.
Lớp 2, liên kết dữ liệu: khung và địa chỉ MAC
Địa chỉ IP nói gói tin cần đi tới đâu. Để đi được chặng đầu tiên, từ máy bạn tới router nhà, cần một lớp nữa: lớp liên kết dữ liệu, lo việc chuyển một khung giữa hai thiết bị nối trực tiếp với nhau (cùng một mạng Wi-Fi, hay cùng một sợi cáp Ethernet).
- Mỗi card mạng có một địa chỉ MAC (48 bit), chỉ có ý nghĩa trong mạng cục bộ. Khung gửi từ máy bạn ghi MAC đích là của router, không phải của Cloudflare; mỗi chặng, khung cũ bị bóc ra và một khung mới được tạo với MAC của chặng kế tiếp, trong khi gói IP bên trong đi nguyên vẹn.
- Máy biết MAC của router nhờ ARP (với IPv4) hay Neighbor Discovery (với IPv6): hỏi to cả mạng cục bộ "ai đang giữ địa chỉ IP này?", và thiết bị đó trả lời bằng MAC của nó.
- Một khung Ethernet hay Wi-Fi chở được tối đa 1.500 byte dữ liệu (MTU).
ifconfigtrên máy đo cho thấy đúngmtu 1500. Đó là nguồn gốc của con số 1.460 ở mục TCP. - Lớp này còn lo phát hiện lỗi: mỗi khung Ethernet kết thúc bằng 4 byte kiểm tra (CRC); khung hỏng bị bỏ, và TCP ở trên sẽ gửi lại.
Lớp 1, vật lý: bit thành tín hiệu
Dưới cùng, mỗi bit phải trở thành một thứ vật lý: điện áp trên dây đồng, sóng radio của Wi-Fi, hay xung ánh sáng trong cáp quang. Lớp vật lý quyết định tốc độ tối đa (bao nhiêu bit mỗi giây) và, quan trọng hơn cả, độ trễ không thể vượt qua.
Ánh sáng trong cáp quang đi khoảng 200.000 km mỗi giây, khoảng hai phần ba tốc độ trong chân không. RTT nhỏ nhất đo được tới Cloudflare là 55 ms, tức khoảng 27 ms mỗi chiều; trong thời gian đó, ánh sáng trong cáp quang đi được khoảng 5.500 km. Thực tế máy chủ gần hơn thế nhiều: phần lớn thời gian là xếp hàng và xử lý ở các router và ở chặng không dây, không phải quãng đường. Nhưng giới hạn vật lý thì có thật: một máy chủ ở bên kia Trái Đất, khoảng 20.000 km, không bao giờ có RTT dưới khoảng 200 ms, dù mạng có nhanh đến đâu. Đó là lý do các CDN như Cloudflare đặt máy chủ ở khắp nơi: thứ duy nhất làm giảm được độ trễ vật lý là ở gần hơn.
Một gói tin được bọc thế nào
Ghép tất cả lại, một đoạn dữ liệu đầy (1.348 byte, theo MSS đo được) của trang web khi rời máy bạn trông như sau (với IPv4, kích thước header điển hình):
| Phần | Byte | Của lớp |
|---|---|---|
| Header Ethernet (MAC nguồn, MAC đích, loại) | 14 | 2 |
| Header IPv4 (địa chỉ IP, TTL…) | 20 | 3 |
| Header TCP (cổng, số thứ tự…, kèm tùy chọn timestamp) | 32 | 4 |
| Phần đầu và phần xác thực của bản ghi TLS | ~22 | 5 |
| Dữ liệu HTTP/2 thật sự | phần còn lại | 5 |
| Mã kiểm tra Ethernet (CRC) | 4 | 2 |
Khoảng 90 byte cho các lớp quản lý trên mỗi gói tin khoảng 1.400 byte: chưa tới 7%. Nhưng với những gói nhỏ (một tin nhắn chat vài byte, hay một lần xác nhận TCP), phần "phong bì" có thể lớn hơn cả nội dung, giống như chi phí system call ở bài trước: mỗi lần gửi có một giá cố định.
Phía sau Cloudflare
Tới máy chủ của Cloudflare, gói tin đi ngược lên đủ năm lớp, TLS được mở ra, và yêu cầu HTTP được đọc. Vì trang không được giữ sẵn, Cloudflare mở (hoặc dùng lại) một kết nối khác tới máy chủ gốc. Ở đó, theo cách trang này được triển khai, Caddy (một reverse proxy, có ở bài streaming của seri Python) nhận yêu cầu và chuyển cho uvicorn, nơi một hàm async def của FastAPI tạo ra trang HTML, đúng cái event loop mà bài đầu của seri Python đã tự viết lại bằng epoll. Câu trả lời đi ngược lại cả con đường đó.
Mọi seri trên trang này gặp nhau ở đây: con số và phép cộng, CPU và cache, bộ nhớ, hệ điều hành và system call, một trình thông dịch Python, một server bất đồng bộ, và năm lớp mạng, tất cả để đưa vài nghìn byte chữ tới màn hình của bạn trong khoảng nửa giây.
Bài học thực tế
- RTT là thứ đắt nhất. Mỗi lần bắt tay là một RTT; một trang cần DNS, TCP, TLS rồi HTTP là ít nhất ba tới bốn RTT trước khi có byte đầu tiên. Giảm số lần đi về quan trọng hơn tăng băng thông.
- Dùng lại kết nối. Yêu cầu thứ hai trên cùng một kết nối không phải bắt tay nữa: 130 ms thay vì hơn nửa giây. HTTP/2 cho nhiều yêu cầu chạy chung một kết nối; HTTP/3 rút gọn cả bắt tay.
- Ở gần người dùng. Không thể vượt tốc độ ánh sáng, chỉ có thể rút ngắn quãng đường: CDN và cache ở rìa mạng.
- Cache đúng chỗ.
cf-cache-status: DYNAMICnghĩa là mọi lượt xem đều phải về tận máy chủ gốc; một trang được giữ sẵn ở rìa sẽ trả lời chỉ sau khoảng một RTT. - Nén văn bản. 23 KB thành 6,6 KB chỉ bằng gzip.
- Gói nhỏ thì đắt. Gom dữ liệu thành gói lớn hơn, như mọi ranh giới khác trong seri này.
Tóm lại
| Lớp | Lo việc gì | Đo được trên viet.sarl |
|---|---|---|
| 5. Ứng dụng: DNS | Tên miền thành địa chỉ IP | 232 ms khi chưa nhớ, 2 ms khi đã nhớ; TTL 300 s |
| 5. Ứng dụng: TLS | Xác minh, mã hóa, chọn giao thức | TLS 1.3, chứng chỉ Let's Encrypt, HTTP/2; một RTT |
| 5. Ứng dụng: HTTP | Yêu cầu và trả về nội dung | 83 byte yêu cầu; 23 KB nén còn 6,6 KB |
| 4. Giao vận: TCP | Dòng byte tin cậy, đúng thứ tự | Bắt tay một RTT; MSS 1.348 byte |
| 3. Mạng: IP | Đưa gói tới đích qua nhiều router | 13 chặng; Cloudflare dùng anycast |
| 2. Liên kết dữ liệu | Khung giữa hai thiết bị kề nhau | MTU 1.500 byte; địa chỉ MAC đổi ở mỗi chặng |
| 1. Vật lý | Bit thành tín hiệu | RTT nhỏ nhất 55 ms; ánh sáng trong cáp quang ~200.000 km/s |
Năm bài, từ một phép cộng trong vài trăm transistor tới một trang web đi nửa vòng mạng Internet. Không có tầng nào là phép màu: chỉ là những lớp trừu tượng xếp chồng lên nhau, mỗi lớp giải quyết một vấn đề, và mỗi ranh giới giữa hai lớp có một cái giá mà khi hiểu rồi thì đo được, và tránh được.