Bài 00 · Trang phụ · đọc thêm, không bắt buộc

Đọc output thật, từng dòng một

Ba lab của Bài 00 cho ra những dòng output thật, chạy trên máy mình ngày 24/09/2026: Windows 11, Git Bash, WiFi ở nhà. Trang này đọc từng dòng, gắn mỗi dòng vào một chặng trên bản đồ, và giải thích những chỗ output lệch khỏi tài liệu mẫu.

Output thật Bài giảng 00

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:

  1. Ai in ra dòng này? Chính công cụ đang chạy, hay một máy ở đầu bên kia?
  2. Dòng này nói về chặng nào trên bản đồ chín chặng của Bài 00?
  3. 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
Hình 1 Output thật của 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
Hình 2 Đường đi điển hình của một câu hỏi DNS. Mỗi tầng giữa có thể trả lời ngay từ những gì nó đã nhớ, không cần đi tiếp xuống dưới.
Bảng danh sách root server trên trang IANA: mười ba tên từ a.root-servers.net tới m.root-servers.net, mỗi tên kèm một địa chỉ IPv4, một địa chỉ IPv6 và tổ chức vận hành.
Ảnh 1 Danh sách server gốc (root server) do IANA công bố. IANA là tổ chức quản lý số hiệu của Internet. Danh sách có mười ba tên, từ 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
Hình 3 Truy vấn một tên miền không tồn tại, bước tự gây lỗi của Lab 1.

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.

Chốt

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.

Bảng 1 Ký tự đầu dòng trong output của curl -v.
Ký tựNghĩaThuộ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 headerGửi request HTTP
<Dữ liệu nhận về: dòng trạng thái và các header trả lờiCâ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
Hình 4 Phần hỏi DNS trong output thật của 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
Hình 5 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
Hình 6 Phần bắt tay TLS, ngắn hơn hẳn output trong tài liệu viết trên Linux.

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.

Một giả thuyết đã được kiểm lại

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: */*
>
Hình 7 Request HTTP mà 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
Hình 8 Các header trả lời, nguyên văn.

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.

Bảng 2 Ba header đáng chú ý trong câu trả lời của example.com.
DòngNói lên điều gì
Server: cloudflareCâ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: HITCloudflare 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: 3889Bản lưu đó đã nằm sẵn ở đó 3889 giây, tức khoảng 65 phút.
Trang example.com mở trong trình duyệt: một biểu tượng cuốn sách nhỏ và một đoạn chữ giải thích tên miền này dành cho ví dụ trong tài liệu.
Ảnh 2 Phần body mà 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.

Bảng 3 Các phần của câu lệnh đọc chứng chỉ.
Phần lệnhVì 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:443Mở kết nối TCP tới máy và port cần kiểm tra.
-servername example.comGửi SNI: cho server biết client muốn chứng chỉ của tên nào (tab trước).
2>/dev/nullGiấu các dòng chẩn đoán, chỉ giữ lại chứng chỉ.
| openssl x509 -noout -subject -issuer -datesDấ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
Hình 9 Output thật khi đọc chứng chỉ của example.com, 24/09/2026.
Bảng 4 Nghĩa của từng dòng.
DòngNghĩa
subject=CN=example.comChứ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.
notBeforeCó hiệu lực từ 29/07/2026.
notAfterHết hạn ngày 27/10/2026.
Bẫy khi đọc trường issuer

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ốt

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ì

Bảng 5 Chỗ output lệch so với tài liệu mẫu, và bài học rút ra.
Chỗ lệchDo đâuBài học
Không thấy dòng chứng chỉ trong curl -vThư viện TLSCù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ỉDNSMộ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 CloudflareHạ tầng của bên thứ baHeader 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ácCá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

  1. RFC 1034 và RFC 1035, Domain Names: khái niệm và giao thức DNS. rfc-editor.org/rfc/rfc1034
  2. IANA, danh sách root server. iana.org/domains/root/servers
  3. RFC 6066, TLS Extensions, mục Server Name Indication. rfc-editor.org/rfc/rfc6066
  4. RFC 7301, TLS Application-Layer Protocol Negotiation (ALPN). rfc-editor.org/rfc/rfc7301
  5. RFC 9110, HTTP Semantics; RFC 9111, HTTP Caching (header Age). rfc-editor.org/rfc/rfc9111
  6. curl, trang hướng dẫn chính thức. curl.se/docs/manpage.html
  7. 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.

  1. Ảnh 1Danh sách root server, chụp màn hình iana.org, 03/10/2026.
  2. Ảnh 2Trang example.com, chụp màn hình example.com, 30/09/2026.