Cách đọc một dòng output
Vì sao output trên máy bạn hay khác output trong tài liệu? Output trong tài liệu mẫu thường đẹp, vì người viết chọn sẵn một môi trường sạch. Máy thật thì không sạch như vậy, nó là tổ hợp của một hệ điều hành, một bản công cụ cụ thể, một nhà mạng và một chiếc router. Vì vậy output trên máy bạn hay khác tài liệu, và chỗ khác đó chính là chỗ có nhiều thứ để học. Mỗi dòng trên trang này đọc bằng ba câu hỏi giống nhau:
- Ai in ra dòng này? Chính công cụ đang chạy, hay một máy ở đầu bên kia?
- Dòng này nói về chặng nào trên bản đồ chín chặng của Bài 00?
- Nó cho biết gì về các chặng đứng trước? Một dòng chỉ in ra được khi những chặng trước nó đã chạy xong.
nslookup github.com: bước hỏi DNS
Server: (tên router, đã ẩn)
Address: fe80::10
Non-authoritative answer:
Name: github.com
Address: 20.205.243.166
nslookup github.com trong Git Bash, 24/09/2026.
Tên router trong nhà đã được ẩn.nslookup lấy câu trả lời ở đâu? Nó không tự biết. Nó đóng gói câu hỏi "tên này ứng với địa chỉ nào",
gửi tới server DNS mà hệ điều hành được cấu hình để dùng, rồi in lại câu trả lời nhận được. Vì vậy output luôn chia
làm hai khối, khối đầu cho biết ai đã trả lời và khối sau cho biết câu trả lời là gì. Khi chẩn
đoán thì đọc khối đầu trước, vì một câu trả lời sai thường bắt nguồn từ việc hỏi nhầm chỗ.
Server: cho biết ai đang trả lời câu hỏi DNS
Dòng Server: có phải là GitHub không? Không, đó là chiếc router WiFi trong nhà, đang làm server DNS
cho mọi thiết bị trong mạng. Máy người dùng không hỏi thẳng GitHub, và cũng không hỏi được, vì muốn hỏi GitHub thì
phải biết địa chỉ của GitHub, mà đó lại chính là thứ đang cần tìm.
Vì vậy máy hỏi router. Nếu router chưa nhớ sẵn câu trả lời thì nó chuyển câu hỏi lên một server DNS lớn hơn, thường là của nhà mạng, và server đó đi hỏi lần lượt từ gốc xuống như Hình 2.
Có cần nhớ tên từng loại server trên đường đi đó không? Chưa cần. Chỉ có một từ cần biết ngay, vì nó nằm trong output, là có thẩm quyền (authoritative). Server có thẩm quyền là server được chủ tên miền giao giữ bản ghi gốc. Câu trả lời của nó là nguồn sự thật, còn mọi câu trả lời khác chỉ là bản sao.
máy người dùng
│ "github.com là địa chỉ nào?"
▼
router trong nhà ── đã nhớ sẵn? ──→ có: trả lời ngay (non-authoritative)
│ chưa
▼
máy chủ DNS đệ quy (thường của nhà mạng)
│ hỏi lần lượt từ gốc xuống:
├──→ máy chủ gốc (root) "đuôi .com thì hỏi máy chủ .com"
├──→ máy chủ của đuôi .com "github.com thì hỏi máy chủ của GitHub"
└──→ máy chủ của GitHub "20.205.243.166" ← authoritative
a.root-servers.net tới m.root-servers.net, do mười hai tổ chức vận hành. Theo
chính trang này, đằng sau mười ba tên là một mạng gồm hàng trăm server đặt ở nhiều nước. Đây là đỉnh của Hình 2:
mọi lần hỏi từ gốc đều bắt đầu ở một trong mười ba tên này.Address: fe80::10 là địa chỉ của router
Máy liên lạc với router qua một địa chỉ bắt đầu bằng fe80. Đây là loại địa chỉ đặc biệt, chỉ có
nghĩa trong mạng nội bộ đang dùng, và không bao giờ được chuyển ra Internet.
Non-authoritative answer: trả lời từ bản đã lưu
Dòng này cho biết câu trả lời không đến thẳng từ server có thẩm quyền, mà đến từ một bản đã lưu. Bản lưu đó ở đâu
ra? Trước đây router đã hỏi github.com một lần và lưu câu trả lời vào bộ nhớ đệm
(cache), giờ nó đọc lại bản lưu đó.
Bản lưu được giữ bao lâu? Mỗi bản ghi DNS được chủ tên miền gắn một con số gọi là TTL (time to live), là số giây bản ghi được phép nằm trong cache trước khi phải hỏi lại nguồn gốc. Chuyện này ảnh hưởng tới lúc đưa ứng dụng lên Internet. Đổi một bản ghi DNS chỉ mất một giây, nhưng các cache trên khắp thế giới vẫn trả lời bản cũ cho tới khi TTL của chúng hết, mỗi cache một thời điểm khác nhau.
Tên miền không tồn tại: request dừng ở bước hỏi DNS
*** (tên router, đã ẩn) can't find khong-ton-tai-dau-nhe-12345.com: Non-existent domain
Cụm Non-existent domain chính là NXDOMAIN, mã trả lời của DNS cho trường hợp tên không
tồn tại. Mã này có nghĩa chặt: câu hỏi đã được chuyển tới nơi có thẩm quyền, và nơi đó xác nhận tên này không tồn
tại. Nếu không liên lạc được với server DNS thì sẽ ra một lỗi khác. Với một tên .com chưa từng được
đăng ký, nơi có thẩm quyền là server phụ trách đuôi .com.
Giờ hỏi lại hai trong ba câu ở đầu trang. Ai in ra dòng này? Vẫn là router. Nó hỏi lên trên, nhận về câu "không có tên này", rồi chuyển lời lại, vì tên này không ứng với máy nào nên không có server nào để trả lời. Nó cho biết gì về các chặng đứng trước? Request dừng ở bước hỏi DNS. Chưa có kết nối TCP nào được mở, nên chưa có lý do gì để nghi server hay firewall.
Output của nslookup trả lời hai câu: ai đã trả lời câu hỏi DNS, và câu
trả lời là gì. Câu trả lời thường đến từ cache, nên có thể cũ tới khi TTL hết. NXDOMAIN nghĩa
là request dừng ngay ở bước hỏi DNS.
Khi DNS đã trả lời đúng thì những chặng sau diễn ra thế nào? Output của curl -v kể lại
từng bước.
Output của curl -v https://example.com là khối nhiều thông tin nhất của cả ba
lab, nên đọc làm hai lượt. Lượt đầu là những gì diễn ra trước khi HTTP bắt đầu, gồm hỏi DNS, mở kết nối và
bắt tay TLS.
Ký tự đầu dòng: ai đang nói
Mỗi dòng trong output mở đầu bằng một ký tự, ký tự đó để làm gì? Cờ -v (verbose) bảo
curl kể lại từng bước nó làm, và ký tự đầu mỗi dòng cho biết dòng đó do ai "nói" và đi theo chiều nào.
Quy ước này ổn định giữa các phiên bản curl, nên chỉ cần học một lần.
| Ký tự | Nghĩa | Thuộc bước nào |
|---|---|---|
* | Lời curl tự kể: đang hỏi DNS, đang kết nối, đang bắt tay TLS… | Hỏi DNS, mở kết nối, bắt tay TLS |
> | Dữ liệu gửi đi: dòng request và các header | Gửi request HTTP |
< | Dữ liệu nhận về: dòng trạng thái và các header trả lời | Câu trả lời từ reverse proxy (và những gì đứng sau nó) |
Hỏi DNS: một cái tên, bốn địa chỉ
* Host example.com:443 was resolved.
* IPv6: 2606:4700:10::ac42:93f3, 2606:4700:10::6814:179a
* IPv4: 104.20.23.154, 172.66.147.243
curl -v.:443 do curl tự điền. URL gõ vào không có port, nên curl
điền port mặc định của https, đúng như bước tách URL ở bài giảng.
Chỗ đáng chú ý hơn là một cái tên trả về bốn địa chỉ, gồm hai địa chỉ IPv6 và hai địa chỉ IPv4. IPv4 là loại địa chỉ bốn số quen thuộc. IPv6 là thế hệ địa chỉ mới, dài hơn nhiều, viết bằng các nhóm chữ số và chữ cái ngăn bởi dấu hai chấm.
Một tên có nhiều địa chỉ để làm gì? DNS cho phép như vậy, và client được tự do chọn, địa chỉ thứ nhất không kết nối được thì nó thử địa chỉ tiếp theo. Khi mỗi địa chỉ dẫn tới một máy khác nhau thì một máy chết, các máy còn lại vẫn phục vụ. Đó là một cách chia tải và dự phòng có từ rất sớm, chạy ngay ở bước hỏi DNS.
Mở kết nối: thử IPv6, rồi IPv4
* Trying [2606:4700:10::ac42:93f3]:443...
* Trying 104.20.23.154:443...
* Established connection to example.com (104.20.23.154 port 443) from 192.168.10.38 port 55289
curl thử IPv6 trước, rồi IPv4, và kết nối thành công qua IPv4.Có cả hai loại địa chỉ thì client dùng loại nào? Nó thử IPv6 trước, nhưng chỉ chờ một khoảng rất ngắn. Chưa thấy hồi âm thì nó mở song song một kết nối IPv4, và đường nào xong trước thì dùng đường đó. Ở đây kết nối IPv4 xong trước.
Điều đó có chứng minh mạng này không có IPv6 không? Nó chỉ gợi ý vậy, chưa chứng minh được, vì một lần thử IPv6
chỉ chậm hơn một chút cũng cho ra đúng output này. Địa chỉ fe80::10 của router ở tab trước cũng không
giúp phân biệt, vì giao diện mạng nào có IPv6 cũng mang một địa chỉ fe80, kể cả ở mạng có IPv6 đầy đủ.
Muốn biết chắc thì chạy curl -6 https://example.com. Cờ -6 buộc curl chỉ dùng IPv6, nên
lệnh thành công hay báo lỗi sẽ trả lời dứt khoát.
Dòng Established connection cho biết bước mở kết nối đã xong, tức kết nối TCP đã mở. Dòng này ghi
bốn con số, hai của phía server và hai của phía máy người dùng. Trong đó 55289 là port phía máy người
dùng, do hệ điều hành tự chọn cho kết nối này.
Bắt tay TLS, và chỗ "thiếu" so với tài liệu mẫu
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
Vì sao output này không có thông tin chứng chỉ như tài liệu mẫu? Vì curl không tự làm phần mật mã.
Nó giao việc bắt tay TLS cho một thư viện bên dưới, và cùng một chương trình curl có thể được dựng với
những thư viện khác nhau.
Chữ schannel ở dòng đầu là tên thư viện TLS có sẵn trong Windows, và nó là bằng chứng cho thấy bản
curl của Git Bash dùng thư viện này. Thư viện đó không in thông tin chứng chỉ như OpenSSL, thư viện hay
dùng trên Linux, nên output thiếu hẳn mấy dòng subject, expire date mà tài liệu mẫu có. Đó
là lý do Lab 3 phải đọc chứng chỉ bằng openssl.
Giả thuyết đầu tiên của mình là Git Bash đã gọi nhầm bản curl.exe có sẵn của Windows. Mình
kiểm nó bằng một lệnh, không tin luôn:
$ which curl
/mingw64/bin/curl
Đó đúng là curl của Git Bash, nên giả thuyết sai. Gốc của chuyện này là thư viện TLS bên
dưới. Giả thuyết nghe hợp lý đến đâu cũng phải kiểm bằng một lệnh cụ thể trước khi ghi lại. Lệnh
curl -V (chữ V hoa) in ra thư viện TLS mà một bản curl đang dùng.
Hai dòng ALPN là chỗ client và server thống nhất sẽ nói phiên bản HTTP nào, ngay trong lúc bắt tay.
Ở đây cả hai chọn http/1.1, và với bài này thì chỉ cần biết hai dòng đó thuộc bước bắt tay TLS.
Nửa sau của output curl -v gồm request HTTP đi ra và câu trả lời đi về. Trong
câu trả lời có dấu vết của một reverse proxy đang chạy thật.
Request HTTP đi ra
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.21.0
> Accept: */*
>
curl gửi đi, nguyên văn.Một request HTTP gồm những gì? Dòng đầu có ba phần, là phương thức (GET), đích (/, tức
phần path của URL) và phiên bản giao thức. Tiếp theo là các header, mỗi header có dạng Tên: giá trị.
Còn dòng > trống ở cuối thì có phải lỗi hiển thị không? Không. Theo chuẩn, một dòng trống đánh dấu
hết phần header, và thứ gì theo sau là body. GET không có body, nên request dừng tại đó.
Dòng Host: example.com là thứ cho phép một địa chỉ IP phục vụ nhiều website. Máy ở
104.20.23.154 có thể đang phục vụ hàng nghìn website, và nó đọc đúng dòng này để biết request dành cho
website nào. Vì vậy HTTP/1.1 bắt buộc mọi request phải có header Host.
Với HTTPS thì còn vướng một chỗ. Server phải chọn chứng chỉ để trình ra ngay trong lúc bắt tay TLS, nghĩa là
trước khi đọc được header Host. Vậy nó dựa vào đâu để chọn? Dựa vào phần mở rộng
SNI (Server Name Indication), trong đó client ghi sẵn tên miền mình muốn vào gói tin mở
đầu của bắt tay TLS. Đây chính là lý do lệnh openssl ở Lab 3 phải có cờ
-servername example.com.
Câu trả lời đi về, qua một reverse proxy thật
< HTTP/1.1 200 OK
< Server: cloudflare
< Transfer-Encoding: chunked
< Connection: keep-alive
< Age: 3889
< cf-cache-status: HIT
< CF-RAY: a40142dcc8e87164-HKG
Dòng đầu của câu trả lời là dòng trạng thái, gồm phiên bản giao thức, mã 200 và cụm
OK. Mã trạng thái là cách server cho biết request được xử lý ra sao. Ở đây chỉ cần một điều: nhận được
một dòng trạng thái nghĩa là mọi bước trước đó đều đã chạy tốt, gồm hỏi DNS, mở kết nối, bắt tay TLS và gửi request.
Chỉ cần một bước trong số đó hỏng là không có dòng trạng thái nào về được tới curl.
Các header phía sau còn cho biết thêm ai đã trả lời.
| Dòng | Nói lên điều gì |
|---|---|
Server: cloudflare | Câu trả lời cho example.com không đến trực tiếp từ máy của chủ website. Có một reverse proxy đứng trước, do công ty Cloudflare vận hành: đúng vị trí của Nginx trên bản đồ. |
cf-cache-status: HIT | Cloudflare trả lời từ bản lưu (cache) của chính nó. Request này không đi tới máy của chủ website. |
Age: 3889 | Bản lưu đó đã nằm sẵn ở đó 3889 giây, tức khoảng 65 phút. |
curl -v nhận về, khi được trình duyệt dựng thành
trang. curl chỉ in HTML thô ra màn hình, còn trình duyệt đọc HTML rồi mới vẽ. Chính nội dung
trang nhắc lại điều bài học đã dùng: tên miền này chỉ dành cho ví dụ trong tài liệu.Hình 6 không in gì về chứng chỉ. Muốn xem chứng chỉ mà server trình ra lúc bắt tay TLS thì làm thế nào? Tab sau trả lời câu đó.
Đọc chứng chỉ của example.com
openssl s_client là một client TLS tối giản. Nó tự bắt tay với server rồi in ra những gì nhận được,
kể cả chứng chỉ ở dạng mã hoá văn bản. Nối output đó vào openssl x509 thì chứng chỉ được giải mã thành
các trường đọc được. Mỗi phần của câu lệnh có một lý do tồn tại, như Bảng 3 ghi.
| Phần lệnh | Vì sao cần |
|---|---|
echo | | Đưa vào một input rỗng rồi đóng lại, để s_client bắt tay xong là thoát thay vì đứng chờ bạn gõ. |
-connect example.com:443 | Mở kết nối TCP tới máy và port cần kiểm tra. |
-servername example.com | Gửi SNI: cho server biết client muốn chứng chỉ của tên nào (tab trước). |
2>/dev/null | Giấu các dòng chẩn đoán, chỉ giữ lại chứng chỉ. |
| openssl x509 -noout -subject -issuer -dates | Dấu | đưa output của lệnh trước làm input cho lệnh sau. Lệnh sau giải mã chứng chỉ và chỉ in ba nhóm trường. |
$ echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
subject=CN=example.com
issuer=C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
notBefore=Jul 29 22:10:08 2026 GMT
notAfter=Oct 27 22:17:21 2026 GMT
example.com, 24/09/2026.| Dòng | Nghĩa |
|---|---|
subject=CN=example.com | Chứng chỉ này cấp cho tên miền nào. CN là viết tắt của Common Name. |
issuer=… | Ai đã ký chứng chỉ. Tổ chức đứng tên (O) là SSL Corporation. Tên (CN) mang chữ Cloudflare vì đây là một đơn vị ký được lập riêng để cấp chứng chỉ cho Cloudflare. |
notBefore | Có hiệu lực từ 29/07/2026. |
notAfter | Hết hạn ngày 27/10/2026. |
Thấy chữ Cloudflare trong CN, rất dễ kết luận Cloudflare tự cấp chứng chỉ cho mình. Đọc
tiếp trường O=SSL Corporation thì thấy không phải. Đọc từng trường một, đừng
suy ra cả dòng từ cái tên nổi nhất trong đó.
Hai điều tấm chứng chỉ này cho thấy
Điều thứ nhất là nó chỉ có hiệu lực 90 ngày, từ 29/07 tới 27/10, và ngày chạy lab (24/09/2026)
nó còn 33 ngày. Hạn ngắn như vậy nói lên điều gì? Ở đâu đó trong hạ tầng của Cloudflare có một tác vụ tự động, đã
được hẹn giờ để gia hạn chứng chỉ trước 27/10. Nếu tác vụ đó hỏng mà không ai phát hiện thì
example.com sẽ hỏng ở bước bắt tay TLS, dù không ai đụng vào hệ thống.
Điều thứ hai là nó được cấp cho một tên. Rất dễ nghĩ chứng chỉ là thứ gắn tên miền với địa chỉ
IP, nhưng việc đó là của DNS. Chứng chỉ chứng minh máy đang trả lời có quyền dùng tên example.com, chứ
nó không cho biết tên đó ứng với địa chỉ nào.
Có hai chỗ cần nói thêm cho đủ. Một là chuẩn có cho phép ghi cả địa chỉ IP vào chứng chỉ, nhưng chứng chỉ của một website thông thường được cấp theo tên miền. Hai là lệnh ở Lab 3 chỉ in ba nhóm trường, nên Hình 9 không cho thấy hết nội dung của chứng chỉ.
Chứng chỉ mà server trình ra lúc bắt tay TLS trả lời ba câu: cấp cho tên miền nào, ai ký, và hiệu lực tới bao giờ. Hạn dùng ngắn biến việc gia hạn thành việc bắt buộc phải tự động hoá.
Những chỗ lệch dạy được gì
| Chỗ lệch | Do đâu | Bài học |
|---|---|---|
Không thấy dòng chứng chỉ trong curl -v | Thư viện TLS | Cùng tên lệnh, khác thư viện bên dưới thì khác output |
| Một tên trả về bốn địa chỉ | DNS | Một tên có thể trỏ tới nhiều máy. Việc dự phòng bắt đầu ngay ở bước hỏi DNS |
example.com được trả lời bởi Cloudflare | Hạ tầng của bên thứ ba | Header Server cho biết máy nào đã trả lời. Đó không nhất thiết là máy của chủ website |
| Issuer mang tên Cloudflare nhưng thuộc tổ chức khác | Cách đọc | Đọc từng trường một |
Các chỗ lệch trong Bảng 5 có điểm gì chung? Chỗ nào cũng có một lý do cụ thể, nằm ở một nơi xác định: thư viện bên dưới công cụ, bản ghi DNS của website, hạ tầng đứng trước website, hoặc cách đọc output. Vì vậy khi output trên máy bạn khác tài liệu thì việc cần làm là tìm xem bước nào hoặc thành phần nào gây ra khác biệt. Sửa lệnh cho tới khi output trông giống tài liệu thì không giúp được gì trên một server thật, vì ở đó thường không có tài liệu nào để so.
Nguồn đọc thêm
- RFC 1034 và RFC 1035, Domain Names: khái niệm và giao thức DNS. rfc-editor.org/rfc/rfc1034
- IANA, danh sách root server. iana.org/domains/root/servers
- RFC 6066, TLS Extensions, mục Server Name Indication. rfc-editor.org/rfc/rfc6066
- RFC 7301, TLS Application-Layer Protocol Negotiation (ALPN). rfc-editor.org/rfc/rfc7301
- RFC 9110, HTTP Semantics; RFC 9111, HTTP Caching (header
Age). rfc-editor.org/rfc/rfc9111 - curl, trang hướng dẫn chính thức. curl.se/docs/manpage.html
- OpenSSL, tài liệu lệnh
s_client. docs.openssl.org/master/man1/openssl-s_client
Nguồn ảnh
Cả hai ảnh là ảnh chụp màn hình do mình tự chụp.
- Ảnh 1Danh sách root server, chụp màn hình iana.org, 03/10/2026.
- Ảnh 2Trang example.com, chụp màn hình example.com, 30/09/2026.