Module 0 · Bài 01

IP, port, listen, firewall: vì sao gọi không tới

App chạy bình thường khi bạn gọi bằng localhost, nhưng người khác gọi vào thì không được. Bài này đi sâu vào bước mở kết nối và cho thấy mọi lỗi ở bước đó quy về ba điều kiện, cùng hai triệu chứng chỉ ra điều kiện nào đang thiếu.

Bài 00 dừng ở câu hỏi này (xem lại): App chạy bình thường khi gọi bằng localhost:8080 trên chính máy mình, nhưng người khác gọi vào thì không được. Chặng mở kết nối hỏng ở đâu?

Lúc đầu mình trả lời câu này hơi mơ màng, đại ý là đổi localhost thành một địa chỉ khác, rồi mở cổng đó trên máy mình cho đồng nghiệp vào. Câu trả lời đó nghe thì xuôi, vậy nó thiếu ở đâu? Nó thiếu ở hai cụm "địa chỉ của máy" và "mở cổng", vì mỗi cụm gồm nhiều thứ hơn mình tưởng. Bài này gỡ từng thứ một.

Trên bản đồ Bài 00, bước mở kết nối chỉ là một dòng, ghi rằng máy người dùng mở một kết nối tới server. Dòng đó gồm hai việc. Việc thứ nhất là tìm tới đúng máy, việc thứ hai là tìm tới đúng chương trình trong máy đó. Hình 1 phóng to dòng ấy ra.

  Trình duyệt → DNS → [ mở kết nối ] → Mã hoá → Gửi request → …
                        │
                        ├─ gọi đúng địa chỉ                    (địa chỉ IP)
                        ├─ firewall cho qua                    (firewall)
                        └─ có chương trình listen đúng địa chỉ (port, listen)
Hình 1 Bước mở kết nối trên bản đồ Bài 00, nhìn gần hơn. Muốn kết nối được thì phải đủ cả ba điều kiện; trong ngoặc là khái niệm mà mỗi điều kiện dựa vào, xếp theo thứ tự gói tin gặp chúng.
Tình huống

Giả sử bạn chạy app Spring Boot trên laptop, mở http://localhost:8080 thấy trang hiện ra, rồi gửi đúng link đó cho đồng nghiệp ngồi bên cạnh. Họ bấm vào, báo lỗi ngay. Bạn đổi link thành địa chỉ mạng của laptop, họ thử lại, vẫn không vào được, nhưng lần này trình duyệt quay khá lâu mới báo lỗi. Code chưa đổi dòng nào, app vẫn chạy bình thường trên máy bạn.

Một trục ngang: Types of computer problems by how much debugging them makes your brain stop working, từ None tới A lot. Normal problems nằm ở nửa trái; Networking problems đứng riêng ở đầu A lot. Bên dưới, một người ngồi giữa đống thiết bị mạng nói: trước buổi trưa các gói tin số lẻ bị chậm, sau buổi trưa tới lượt gói số chẵn, ngược hẳn hôm qua. Người kia hỏi: Are you sure you're okay? Người ngồi đáp: I'm fine and I believe in ghosts now!
Ảnh 1 Truyện Networking Problems của xkcd, số 2259, năm 2020. Trục ngang xếp các loại lỗi máy tính theo mức làm người gỡ lỗi mất tỉnh táo; lỗi mạng đứng riêng ở đầu "rất nhiều", và nhân vật trong truyện đã tới chỗ tin là có ma. Truyện nói quá cho vui, nhưng cảm giác thì có thật. Bài này đi đường khác: bước mở kết nối chỉ cần ba điều kiện, và mỗi triệu chứng chỉ ra điều kiện nào đang thiếu.

Hai lần đều là "không vào được", vậy sao một lần báo ngay còn một lần chờ lâu? Vì hai triệu chứng khác nhau thường đến từ hai lý do khác nhau. Không biết lý do thì dễ thành Ảnh 1, tức là tắt firewall, đổi port, khởi động lại máy, cái nào ăn thì thôi. Mình cũng từng nói luôn là tắt firewall đi cho nhanh, và tab Firewall cho thấy vì sao làm thế là sai.

Học tới đây mình hỏi lại: bản chất của host là gì nhỉ? Lúc thì thấy 127, lúc thì 192, rồi lại nghe nói card WiFi, mình chả biết đó là một thứ hay nhiều thứ. Từ chỗ rối đó, câu hỏi của bài tách ra thành bốn câu nhỏ:

  1. Một máy có những địa chỉ IP nào, và vì sao localhost luôn trỏ về máy của chính người gõ nó?
  2. Trong một máy có hàng chục chương trình, kết nối được giao cho đúng chương trình bằng cách nào?
  3. Firewall chặn ở đâu, và vì sao phải đủ ba điều kiện thì người khác mới gọi tới được?
  4. Khi thiếu một điều kiện, triệu chứng trông thế nào, và refused khác timeout ra sao?

Cần đã học trước

Bài này dùng lại mấy thứ của Bài 00, và đi sâu hơn vào bước mở kết nối:

  • Bản đồ chín chặng, và chỗ của bước mở kết nối trên đó (Bài 00, tab Bản đồ).
  • URL và hai phần host, port: host là tên máy, port là con số đi sau dấu hai chấm (Bài 00, tab URL).
  • DNS đổi tên miền thành địa chỉ IP. Mạng chỉ chuyển gói tin tới địa chỉ dạng số, qua các router. TCP mở một đường truyền tin cậy giữa hai máy (Bài 00, tab Trên máy người dùng).
  • curl, công cụ gửi request từ dòng lệnh, đã dùng ở lab Bài 00.

Lần gửi link đầu hỏng vì nó đưa đồng nghiệp về chính máy của họ, không tới máy bạn. Muốn hiểu chỗ đó thì phải biết một máy có những địa chỉ IP nào, và vì sao localhost lúc nào cũng quay về máy của người gõ.

Một máy có nhiều địa chỉ IP

Laptop của bạn có mấy địa chỉ? Lần sửa link đầu tiên thay localhost bằng "địa chỉ của laptop", như thể laptop chỉ có một địa chỉ. Mình cũng từng gọi 127.0.0.1 là "IPv4 mặc định của máy". Cả hai cách nghĩ đều sai, và muốn thấy sai ở đâu thì phải có ba định nghĩa cho chuẩn trước đã.

IP address địa chỉ IP
Con số định danh một giao diện mạng trên một mạng. Với IPv4, thế hệ địa chỉ phổ biến nhất, nó dài 32 bit và được viết thành bốn số từ 0 tới 255, như 192.168.10.38. Mạng dùng con số này để biết phải chuyển gói tin tới đâu.
Network interface giao diện mạng
Điểm mà một máy nối vào một mạng: card WiFi, cổng cắm cáp mạng, hoặc một "card" ảo do phần mềm tạo ra. Mỗi giao diện mang địa chỉ riêng của nó. Một máy có bao nhiêu giao diện thì có bấy nhiêu địa chỉ.
Loopback giao diện vòng lặp
Một giao diện ảo không nối với dây hay sóng nào. Gói tin gửi tới nó quay ngược vào chính máy đó và không bao giờ đi ra ngoài. Địa chỉ của nó là 127.0.0.1.
Một card mạng máy chủ có tản nhiệt lớn và hai cổng cắm cáp mạng ở mặt gắn vỏ máy.
Ảnh 2 Card mạng Intel X540-T2 dùng cho server, có hai cổng cắm cáp mạng. Hệ điều hành thấy đây là hai giao diện mạng riêng biệt, mỗi giao diện mang một địa chỉ IP riêng. Địa chỉ gắn với giao diện, không gắn với cả cái máy.

Ba định nghĩa trên dẫn tới điều gì? Dẫn tới chỗ địa chỉ gắn với từng giao diện mạng chứ không gắn với cả cái máy, như cái card hai cổng ở Ảnh 2. Vì vậy một cái laptop bình thường có ít nhất hai địa chỉ cùng lúc, và hay gặp là ba bốn địa chỉ:

  • địa chỉ loopback;
  • địa chỉ của card WiFi, do router trong nhà cấp;
  • một địa chỉ nữa trên một card ảo, nếu đang bật VPN. VPN là phần mềm nối máy vào một mạng ở xa qua Internet, như thể máy đang ngồi trong mạng đó.

Trên máy mình ngày 22/09, ipconfig in ra hai giao diện là WiFi và một card ảo. Lệnh này không liệt kê loopback, nên cộng thêm loopback thì máy có ba địa chỉ. Hình 2 vẽ ba địa chỉ đó.

             một cái laptop
    ┌────────────────────────────────────┐
    │  lo: giao diện loopback            │
    │  127.0.0.1       chỉ trong máy     │  ← không dây, không sóng,
    │                                    │    gói tin không ra ngoài
    │  wlan0: card WiFi                  │
    │  192.168.10.38   trong mạng WiFi   │  ← router cấp, có thể đổi
    │                                    │
    │  card ảo của VPN                   │
    │  10.8.0.2        trong mạng VPN    │
    └────────────────────────────────────┘
Hình 2 Ba giao diện mạng, ba địa chỉ, cùng trên một laptop. Mỗi địa chỉ chỉ gọi tới được từ chỗ ghi bên cạnh nó.

Ba địa chỉ cùng dẫn vào một máy, vậy dùng cái nào cũng được chứ? Không, vì mỗi địa chỉ chỉ có nghĩa với một nhóm người gọi:

  • 127.0.0.1: chỉ chính máy đó gọi được.
  • 192.168.10.38: các máy cùng WiFi gọi được.
  • 10.8.0.2: các máy trong mạng VPN gọi được.

Đồng nghiệp ngồi cùng WiFi với bạn, nên với họ chỉ có một địa chỉ dùng được là địa chỉ WiFi của laptop bạn. Hồi học, mình được hỏi máy đang có mấy địa chỉ thì lại đi trả lời chuyện dải 127 có bao nhiêu địa chỉ, tức là sang một câu khác mất rồi. Cái địa chỉ WiFi mà mình bỏ sót lại chính là cái làm đồng nghiệp vào được.

Địa chỉ WiFi còn có một chỗ phải để ý là nó đổi được. Router cho máy thuê địa chỉ có thời hạn, bằng một cơ chế tên là DHCP, nên khi hết hạn hoặc khi đổi mạng thì máy có thể nhận địa chỉ khác. Mấy ngày học bài này, cùng một chiếc laptop mà hai lần mình đo ra hai địa chỉ, là 192.168.110.203 và 192.168.10.38. Vì thế đừng ghi cứng địa chỉ mạng nội bộ vào tài liệu hay cấu hình. Server thật thì cần địa chỉ cố định, còn người dùng gọi nó bằng tên miền.

Ba dải địa chỉ đặc biệt

Địa chỉ nào cũng đi được ra Internet chứ? Không. Có vài dải địa chỉ được để riêng cho mục đích đặc biệt, và theo quy ước thì router trên Internet không chuyển gói tin mang các địa chỉ đó ra ngoài. Ba dải hay gặp nhất khác nhau ở chỗ chúng có nghĩa trong vùng rộng tới đâu, như Hình 3 xếp ra.

  127.0.0.1    →  chỉ trong máy này             (loopback)
  fe80::…      →  chỉ trong đoạn mạng trực tiếp  (link-local, IPv6)
  192.168.…    →  chỉ trong mạng nội bộ          (mạng riêng)
  10.…
  172.16.… tới 172.31.…
Hình 3 Ba loại địa chỉ đặc biệt, xếp từ hẹp tới rộng theo chỗ dùng được. Không loại nào được chuyển ra Internet.

Vì sao dòng thứ hai của Hình 3 viết khác hẳn hai dòng kia? Vì nó là địa chỉ IPv6, thế hệ địa chỉ mới, dài 128 bit và viết bằng các nhóm số thập lục phân ngăn nhau bằng dấu hai chấm. Bài này chỉ cần biết IPv6 cũng có loopback, viết là ::1, tương đương 127.0.0.1. Còn vì sao có hai thế hệ địa chỉ chạy song song thì Lịch sử địa chỉ IP kể đầy đủ.

Trong IPv6, địa chỉ bắt đầu bằng fe80 là loại link-local. Loại này chỉ có nghĩa giữa các thiết bị nối trực tiếp với nhau, kiểu laptop với router WiFi, và nó không đi qua được router đó. Lab Bài 00 đã gặp một địa chỉ như vậy, khi nslookup in ra địa chỉ của router.

Còn dải 127 thì có bao nhiêu địa chỉ? Mình từng tính là 127⁴, và sai. Trong bốn số của địa chỉ, số đầu bị khóa cứng là 127, còn ba số sau tự do. Mỗi số dài 8 bit, ba số là 24 bit, nên dải này có 2²⁴ địa chỉ, khoảng 16,7 triệu. Ảnh 3 cho thấy con số đó nằm ở đâu trong sổ đăng ký.

Bảng sổ đăng ký IPv4 của IANA, mỗi dòng một khối địa chỉ; hai dòng 010/8 Private Use và 127/8 Loopback được khoanh màu.
Ảnh 3 Sổ đăng ký không gian địa chỉ IPv4 của IANA, tổ chức quản lý số hiệu của Internet. Mỗi dòng là một khối có chung 8 bit đầu, gồm khoảng 16,7 triệu địa chỉ; cách ghi /8 chính là "8 bit đầu cố định". Hai dòng khoanh là hai dải vừa gặp: 10.0.0.0/8 dành cho mạng riêng và 127.0.0.0/8 dành cho loopback. Cả khối 127 đều là loopback, dù máy thường chỉ dùng một địa chỉ trong đó là 127.0.0.1.

Trong ba dải, dải mạng riêng là dải đụng thẳng vào tình huống của bài. Vì nó không ra Internet nên đồng nghiệp cùng WiFi gọi được 192.168.10.38, còn một người đang ở nhà họ thì không bao giờ gọi được. Lý do là không router nào trên đường đi chịu chuyển gói tin tới một địa chỉ mạng riêng.

Cũng vì thế mà server thật phải nằm ở chỗ có địa chỉ IP công khai, kiểu server thuê trong trung tâm dữ liệu, chứ không chạy trên laptop ở nhà. Lịch sử dẫn tới chuyện này, gồm việc địa chỉ IPv4 hết dần và router gia đình cho cả nhà dùng chung một địa chỉ, nằm ở Lịch sử địa chỉ IP. Còn "cùng mạng" nghĩa là gì, và cái router trong nhà gồm những thiết bị nào, thì Mạng và thiết bị mạng nói rõ.

Đào sâu · các dải này được quy định ở đâu

Mọi điều ở trên đều có văn bản chuẩn đứng sau. Các chuẩn của Internet được công bố thành những văn bản đánh số, gọi là RFC.

  • RFC 1122 quy định dải loopback 127.0.0.0/8, kèm yêu cầu địa chỉ dạng này không bao giờ xuất hiện bên ngoài một máy.
  • RFC 1918 dành ra ba dải mạng riêng: 10.0.0.0/8, 172.16.0.0/12 và 192.168.0.0/16.
  • RFC 4291 định nghĩa loopback ::1 và dải link-local fe80::/10 của IPv6.
  • RFC 6761 xếp cái tên localhost vào nhóm tên đặc biệt, luôn chỉ về loopback.

Vì sao localhost là một từ tương đối

Có đủ các địa chỉ rồi thì giải thích được lần gửi link đầu. localhost là một cái tên, và máy nào cũng dịch nó về loopback của chính nó, tức 127.0.0.1, hoặc ::1 trên nhiều hệ thống mới. Vậy khi đồng nghiệp gõ http://localhost:8080 thì chuyện gì xảy ra? Máy họ dịch tên đó ra loopback của máy họ, nên gói tin đi một vòng qua giao diện loopback rồi quay lại chính máy họ, chưa hề ra khỏi bàn của họ. Máy họ không có app nào ở port 8080, nên họ bị từ chối ngay tại đó.

  máy bạn                              máy đồng nghiệp
  ┌──────────────────────┐             ┌──────────────────────┐
  │ app ở port 8080      │             │ không có app nào     │
  │                      │             │                      │
  │ localhost:8080       │             │ localhost:8080       │
  │   └→ 127.0.0.1 ──┐   │             │   └→ 127.0.0.1 ──┐   │
  │      tới app ←───┘   │             │      bị từ chối ←┘   │
  └──────────────────────┘             └──────────────────────┘
            gói tin của đồng nghiệp không bao giờ đi qua đây
Hình 4 Cùng một đường link, hai máy, hai kết quả. Trên mỗi máy, localhost quay về chính máy đó.

Như vậy localhost không chỉ một máy cố định nào. Nó giống chữ "ở đây", ai nói thì "ở đây" là chỗ người đó đang đứng, nên gửi cho đồng nghiệp chữ "ở đây" thì họ hiểu là chỗ họ ngồi. Muốn họ gọi tới máy mình thì phải đưa một địa chỉ của máy mình mà họ với tới được, tức địa chỉ trong mạng chung của hai người.

Port: con số chọn chương trình

Gói tin đã tới đúng máy rồi thì chương trình nào trong máy nhận nó? Một server bình thường chạy cùng lúc nhiều chương trình nhận kết nối từ mạng, ví dụ một web server, một app Spring Boot và một database PostgreSQL. Địa chỉ IP chỉ đưa gói tin tới máy, chứ không nói chương trình nào trong máy sẽ nhận. Vì vậy mỗi gói tin phải mang thêm một con số thứ hai, để hệ điều hành biết giao nó cho ai.

Port cổng
Con số 16 bit, từ 0 tới 65535, nằm trong phần đầu của mỗi gói tin TCP. Hệ điều hành của máy nhận dùng con số này để chọn chương trình sẽ nhận gói tin. Một kết nối luôn nhắm tới một cặp địa chỉ IP và port, viết là 192.168.10.38:8080.
Sơ đồ phần đầu gói tin TCP trong RFC 9293 vẽ bằng ký tự, hàng đầu tiên gồm hai ô Source Port và Destination Port được khoanh đỏ.
Ảnh 4 Phần đầu của một gói tin TCP, trong văn bản chuẩn RFC 9293. Mỗi hàng rộng 32 bit; hàng đầu tiên được chia đôi cho hai con số 16 bit: Source Port (port của bên gửi) và Destination Port (port của bên nhận). Trong máy không có sẵn thứ gì tên là port, vì port chỉ là con số nằm ở đây, trong từng gói tin.

Vì port chỉ là một trường số 16 bit nên nó không bao giờ lớn hơn 65535. Trong khoảng đó, vài con số được quy ước cho những dịch vụ quen, như 80 cho HTTP, 443 cho HTTPS, 5432 cho PostgreSQL. Còn 8080 thì chỉ là thói quen của dân web khi chạy app trên máy mình, vì nó dễ nhớ và không đụng mấy con số quy ước kia.

Listen: chương trình xin nhận kết nối

Gói tin mang số 8080 tới máy thì có chắc là có người nhận không? Không chắc. Nó chỉ có chỗ đi khi trước đó đã có một chương trình nói với hệ điều hành rằng nó muốn nhận kết nối tới port 8080. Việc đăng ký nhận đó có tên riêng là listen, và điều kiện có chương trình listen đúng địa chỉ trong Hình 1 chính là nói về nó.

Listen lắng nghe
Việc một chương trình đang chạy xin hệ điều hành: "mọi kết nối tới port này hãy giao cho tôi". Hệ điều hành ghi lời xin đó vào một bảng các port đang được listen. Không chương trình nào listen một port thì port đó không có trong bảng.

Ai giữ cái bảng đó? Phần lõi của hệ điều hành, có tên là kernel. Ở bài này chỉ cần biết kernel dùng bảng ra sao. Khi có kết nối tới, nó tra bảng xem port đích có trong đó không. Nếu có thì nó giao kết nối cho chương trình tương ứng, còn nếu không có thì nó từ chối.

Bảng đó xem được. Trên Windows, lệnh netstat -ano in nó ra, mỗi dòng LISTENING là một port đang có chương trình nhận. Cột cuối của mỗi dòng là PID, con số mà hệ điều hành gán cho mỗi chương trình đang chạy.

$ netstat -ano | grep ":8080 " | grep LISTEN
  TCP    127.0.0.1:8080         0.0.0.0:0              LISTENING       25800
Hình 5 Một dòng trong bảng listen: chương trình có PID 25800 đang listen port 8080, nhưng chỉ trên địa chỉ 127.0.0.1. Chạy trên máy mình, Git Bash, 03/10/2026.

Hai chương trình có cùng listen một port được không? Không, nếu là trên cùng một địa chỉ, vì khi đó hệ điều hành không biết giao kết nối cho ai. Chương trình đến sau bị từ chối ngay lúc xin, kèm lỗi address already in use.

Listen address: nhận kết nối gọi tới địa chỉ nào

Vì sao dòng LISTENING ở Hình 5 ghi cả địa chỉ lẫn port, là 127.0.0.1:8080? Vì lúc xin listen, chương trình còn chọn luôn nó nhận kết nối gọi tới địa chỉ nào của máy. Tab trước đã cho thấy một máy có nhiều địa chỉ, và chương trình được quyền chỉ nhận ở vài địa chỉ trong số đó.

Listen address địa chỉ listen
Địa chỉ IP mà chương trình gắn port của nó vào khi listen. Listen 127.0.0.1 nghĩa là chỉ nhận kết nối gọi tới loopback, tức chỉ từ trong máy. Listen một địa chỉ cụ thể như 192.168.10.38 nghĩa là chỉ nhận kết nối gọi tới địa chỉ đó. Listen địa chỉ đặc biệt 0.0.0.0 nghĩa là nhận kết nối gọi tới mọi địa chỉ của máy.

Có định nghĩa này rồi thì phát biểu được điều kiện listen cho chính xác. Lấy một kết nối gọi tới 192.168.10.38:8080 làm ví dụ. Kết nối đó chỉ được giao cho một chương trình khi chương trình ấy đang listen port 8080 trên chính địa chỉ 192.168.10.38, hoặc trên 0.0.0.0. Một chương trình listen 127.0.0.1:8080 thì không nhận kết nối này, dù nó vẫn chạy bình thường và bạn gọi localhost:8080 vẫn thấy trang.

Ví dụ thật ở Hình 6 chạy cùng một server nhỏ hai lần, chỉ đổi địa chỉ listen.

listen 127.0.0.1                   listen 0.0.0.0
$ curl http://127.0.0.1:8080       $ curl http://127.0.0.1:8080
xin chao tu 127.0.0.1              xin chao tu 0.0.0.0

$ curl http://192.168.10.38:8080   $ curl http://192.168.10.38:8080
curl: (7) Failed to connect …      xin chao tu 0.0.0.0
  after 2039 ms
Hình 6 Cùng một server, hai địa chỉ listen. Listen 127.0.0.1 thì gọi bằng địa chỉ mạng của chính máy cũng bị từ chối. Chạy trên máy mình ngày 03/10/2026, output rút gọn; địa chỉ mạng nội bộ thật được thay bằng 192.168.10.38 cho khớp các hình trước. Lab 3 làm lại thí nghiệm này.

Vậy app của bạn đang listen ở đâu? Spring Boot mặc định listen trên mọi địa chỉ của máy, và muốn đổi thì thêm một dòng cấu hình là thuộc tính server.address. Nhiều database và công cụ dev thì làm ngược lại, mặc định chỉ listen 127.0.0.1 cho an toàn.

Hai điều kiện đã có là gọi đúng địa chỉ và có chương trình listen đúng địa chỉ. Điều kiện còn lại nằm giữa hai cái đó trên đường đi của gói tin. Ghép đủ ba thì trả lời được firewall chặn ở đâu, và vì sao phải đủ cả ba thì người khác mới gọi tới được.

Firewall: lớp chặn đứng trước chương trình

Vì sao cần thêm một lớp chặn nữa? Vì một máy nối mạng nhận gói tin từ mọi phía, kể cả từ những máy nó không quen. Nếu không có gì chắn thì ai gửi tới cũng chạm được vào mọi chương trình đang listen. Vì vậy hệ điều hành và thiết bị mạng có thêm một lớp lọc, đứng trước bảng listen. Windows bật sẵn lớp này từ lúc cài máy, và ở tình huống mở bài thì nó là lý do hay gặp nhất làm lần thử thứ hai phải chờ lâu.

Firewall tường lửa
Bộ lọc gói tin theo luật, mỗi luật xét nguồn, đích, port và giao thức của gói tin để quyết định cho qua hay chặn. Nó làm việc trước khi gói tin tới được chương trình đang listen. Firewall có thể là một phần mềm trong hệ điều hành, như Windows Defender Firewall, hoặc một thiết bị riêng đứng ở ranh giới giữa hai mạng.
Mặt trước một thiết bị firewall Netgear với đèn báo của hai cổng WAN và bốn cổng LAN.
Ảnh 5 Một firewall dạng thiết bị riêng, Netgear FVS336G. Mặt trước chia rõ hai phía: hai cổng WAN nối ra Internet, bốn cổng LAN nối vào mạng bên trong. Mọi gói tin đi giữa hai phía đều phải qua bộ luật của nó. Firewall trên laptop cũng làm đúng việc đó, chỉ có điều nó là phần mềm và đứng giữa mạng với các chương trình trong máy.

Firewall chặn một gói tin bằng cách nào? Có hai cách, và mỗi cách để lại một triệu chứng khác ở phía người gọi.

  • Vứt bỏ lặng lẽ (drop): gói tin biến mất, không có gì gửi lại.
  • Từ chối (reject): firewall gửi lại một lời báo rằng kết nối không được phép.

Windows chọn cách nào? Với kết nối đi vào mà không có luật cho phép, firewall của Windows mặc định vứt lặng lẽ. Nhiều firewall khác cũng làm vậy, vì vứt lặng lẽ thì người lạ không biết ở đó có máy nào. Còn vì sao "vứt lặng lẽ" lại thành một lần chờ lâu thì tab sau giải thích.

Ba điều kiện để người khác gọi tới được

Tới đây đã đủ ba điều kiện của bước mở kết nối. Gói tin từ máy đồng nghiệp tới app của bạn gặp chúng theo đúng thứ tự của Hình 7, là gọi đúng địa chỉ, rồi firewall cho qua, rồi có chương trình listen đúng địa chỉ. Thiếu điều kiện nào thì kết nối hỏng ở đó, và mỗi điều kiện hỏng theo một kiểu riêng.

  máy đồng nghiệp
       │  gói tin tới 192.168.10.38:8080
       ▼
  gọi đúng địa chỉ?          không → tới sai máy, bị máy đó từ chối
       ▼
  firewall cho qua?          không → bị vứt lặng lẽ, người gọi chờ tới hết giờ
       ▼
  có chương trình listen     không → hệ điều hành từ chối ngay
  port 8080 trên đúng
  địa chỉ này?
       ▼
  app Spring Boot nhận kết nối
Hình 7 Đường đi của một kết nối từ máy khác vào app của bạn. Ba câu hỏi được xét theo đúng thứ tự từ trên xuống.
Bảng 1 Ba điều kiện để một máy khác trong cùng mạng gọi được app của bạn, xếp theo thứ tự gói tin gặp chúng, và triệu chứng khi thiếu từng điều kiện.
Điều kiệnCụ thể làThiếu thì thấy gì
Gọi đúng địa chỉGọi địa chỉ của máy bạn trong mạng chung, như 192.168.10.38:8080, không phải localhostrefused, ở chính máy người gọi
Firewall cho quaFirewall trên máy bạn cho gói tin từ ngoài vào port 8080timeout
Có chương trình listen đúng địa chỉApp listen trên địa chỉ được gọi tới hoặc trên 0.0.0.0, không chỉ 127.0.0.1refused

Bảng 1 giải thích tình huống mở bài thế nào? Link localhost thiếu điều kiện gọi đúng địa chỉ, nên đồng nghiệp gọi vào chính máy họ và bị từ chối ngay. Link có địa chỉ WiFi thì gọi đúng địa chỉ rồi, nhưng lại thiếu điều kiện firewall cho qua. Firewall trên laptop bạn vứt gói tin lặng lẽ, nên trình duyệt của họ phải chờ.

Bảng còn một cái bẫy. Gọi sai địa chỉ và không có chương trình listen cho ra cùng một triệu chứng là refused, nên gặp refused thì phải kiểm cả hai.

Bảng 1 cũng dùng để kiểm một lần sửa. Mình từng nghĩ mở firewall xong là đồng nghiệp vào được, mà không chắc đâu. Giả sử bạn mở firewall cho port 8080 rồi mà họ vẫn không vào. Nếu triệu chứng đổi từ timeout sang refused thì phần firewall đã sửa đúng, vì gói tin giờ qua được firewall và dừng ở điều kiện kế tiếp, là app đang chỉ listen 127.0.0.1.

Vậy mỗi lần sửa đúng một điều kiện thì triệu chứng đổi sang triệu chứng của điều kiện kế tiếp. Triệu chứng đổi chính là bằng chứng rằng điều kiện vừa sửa đã đạt.

Một thói quen nên tập ngay từ máy dev

Gặp lỗi mạng thì đừng tắt firewall cho nhanh (mình cũng từng nói vậy). Cứ mở đúng một port, cho đúng nguồn cần thiết. Trên Windows, lệnh dưới chạy trong PowerShell (cửa sổ dòng lệnh của Windows, mở bằng quyền quản trị). Nó chỉ cho các máy cùng mạng với máy bạn gọi vào port 8080 (nghĩa của LocalSubnet), mọi thứ khác vẫn bị chặn như cũ:

New-NetFirewallRule -DisplayName "Spring Boot dev 8080" `
  -Direction Inbound -LocalPort 8080 -Protocol TCP `
  -RemoteAddress LocalSubnet -Action Allow

Nguyên tắc đứng sau là least privilege, đặc quyền tối thiểu: mỗi thành phần chỉ được cấp đúng phần quyền nó cần.

Ẩn dụ hỗ trợ ghi nhớ: một tòa nhà

Cả bốn khái niệm đã có định nghĩa và ví dụ thật, vậy cần thêm phép so sánh để làm gì? Để giữ bốn vai tách nhau trong đầu, sao cho lúc gặp lỗi thì hỏi đúng bốn câu theo đúng thứ tự. Phép so sánh dưới đây chỉ làm việc đó, nó không thay cho định nghĩa.

  địa chỉ IP           =  một lối vào của tòa nhà (tòa nhà có nhiều lối vào)
  port                 =  số phòng bên trong tòa nhà
  chương trình listen  =  người ngồi trong phòng, tự chọn tiếp khách
                          đến từ lối vào nào (listen address)
  firewall             =  bảo vệ ở lối vào, lọc ai được đi vào
Hình 8 Ẩn dụ tòa nhà, đặt cạnh bốn định nghĩa của bài. Mỗi vai hỏng theo một kiểu riêng.
Giới hạn của ẩn dụ này

Chữ "phòng" gợi ý port là một không gian có sẵn trong máy. Thực tế không có căn phòng nào: port chỉ là con số 16 bit trong phần đầu gói tin, như Ảnh 4 cho thấy, và hệ điều hành dùng con số đó để tra bảng listen. "Lối vào" cũng gợi ý một thứ vật lý, trong khi một máy có thể tạo ra hàng chục giao diện mạng ảo bằng phần mềm. Khi ẩn dụ và định nghĩa mâu thuẫn, định nghĩa thắng.

Chốt

Người khác gọi tới được app của bạn khi và chỉ khi đủ ba điều kiện: gọi đúng địa chỉ, firewall cho qua, có chương trình listen đúng địa chỉ. Gọi sai địa chỉ hoặc không có chương trình listen cho ra refused; firewall chặn thường cho ra timeout. Sửa đúng một điều kiện thì triệu chứng đổi.

Một kết nối TCP được mở bằng ba gói tin

Ba điều kiện đủ hay thiếu thì người gọi nhìn thấy gì? Muốn trả lời thì phải xem một kết nối được mở ra sao. TCP, giao thức mở một đường truyền tin cậy giữa hai máy đã gặp ở Bài 00, không gửi dữ liệu ngay. Trước khi gửi byte nào, hai máy phải thoả thuận rằng cả hai đã sẵn sàng, và thoả thuận đó gồm ba gói tin trao đổi qua lại. Thoả thuận này hỏng được theo hai kiểu: một kiểu báo lỗi ngay, tên là refused, và một kiểu bắt người gọi chờ, tên là timeout.

Three-way handshake bắt tay ba bước
Cách TCP mở một kết nối. Bên gọi gửi một gói SYN ("tôi muốn mở kết nối tới port này"). Nếu có chương trình đang listen port đó, máy bên kia trả lời SYN-ACK ("đồng ý"). Bên gọi đáp ACK ("đã nhận"), và kết nối được thành lập.
Hình bắt tay ba bước trong RFC 9293: dòng 2 gửi SYN, dòng 3 trả SYN,ACK, dòng 4 gửi ACK, ba dòng được khoanh đỏ, vàng, lam.
Ảnh 6 Bắt tay ba bước trong văn bản chuẩn RFC 9293 của TCP. Bên A gọi, bên B đang listen: A gửi SYN, B trả SYN,ACK, A đáp ACK, và cả hai sang trạng thái ESTABLISHED, tức kết nối đã mở. Chữ LISTEN ở đầu cột B chính là trạng thái listen của tab Port & listen.

Ảnh 7 vẽ lại đúng ba gói tin đó bằng hai cái lon nối dây.

Truyện chín khung. Một người đóng đinh vào đáy lon, nối dây: I am here, just building, my layer 1. Anh ném một lon cho bạn và nói SYN; người bạn bắt lấy, đáp SYN-ACK; anh đáp ACK. Hai người cười: We are so geeks. Totally.
Ảnh 7 Truyện TCP Buddies của Daniel Stori, turnoff.us, năm 2015. Hai người dựng điện thoại ống bơ: người thứ nhất nói SYN, người thứ hai đáp SYN-ACK, người thứ nhất đáp ACK, rồi mới nói chuyện, đúng thứ tự của Ảnh 6. "Layer 1" ở hàng đầu là cách gọi phần vật lý của mạng, ở đây là sợi dây. Điều truyện không vẽ: nếu đầu kia không ai cầm lon, tức không có chương trình nào listen, thì không có SYN-ACK nào quay về.

Kết nối mở xong rồi thì hệ điều hành nhận ra nó bằng gì? Bằng bốn con số, gồm địa chỉ và port của bên gọi, địa chỉ và port của bên nhận, gọi chung là bộ bốn. Port của bên nhận phải cố định và biết trước, như 443 hay 8080, vì nếu không thì bên gọi không biết gọi vào đâu.

Còn port của bên gọi thì ai chọn? Chương trình không tự chọn, mà hệ điều hành lấy một số còn trống gắn vào kết nối, tới khi đóng kết nối thì thu hồi. Vì chỉ dùng tạm như vậy nên nó có tên là ephemeral port, port tạm. Dòng dưới đây lấy từ output curl -v ở lab Bài 00, và nó in đủ bộ bốn của một kết nối thật.

* Established connection to example.com (104.20.23.154 port 443)
                                         └─────┬─────┘      └┬┘
                                         địa chỉ bên nhận  port bên nhận
  from 192.168.10.38 port 55289
       └─────┬─────┘      └─┬─┘
       địa chỉ bên gọi    port tạm: hệ điều hành tự cấp
Hình 9 Bộ bốn của một kết nối TCP, đọc từ output curl -v ở lab Bài 00, chạy trên máy mình ngày 24/09/2026.

Nhờ port tạm mà mười tab trình duyệt cùng mở example.com vẫn là mười kết nối riêng, vì mỗi tab có một port tạm khác.

Refused: có người trả lời "không"

Kiểu hỏng thứ nhất xảy ra khi nào? Khi gói SYN tới đúng một máy đang chạy, nhưng trên máy đó không chương trình nào listen port đích. Máy đó không im lặng. Kernel của nó, tức phần lõi hệ điều hành, chủ động gửi lại một gói đặc biệt để bên gọi biết kết nối không mở được.

RST gói reset
Gói TCP mang cờ RST, nghĩa là "kết nối này không tồn tại". Hệ điều hành gửi nó khi một gói SYN tới một port không có trong bảng listen.
Refused bị từ chối
Triệu chứng mà bên gọi thấy khi nhận được gói RST: thư viện mạng dịch nó thành lỗi Connection refused, và curl in ra mã lỗi (7).

Chỗ cần nhớ nhất là refused chỉ xảy ra khi máy đích còn sống, vì phải có hệ điều hành đang chạy thì mới gửi được gói RST. Mình từng nghĩ ngược lại, rằng máy còn sống thì không bao giờ bị refused. Mình cũng từng nói không ai listen thì người gọi thấy timeout, và cũng sai, vì không ai listen mà gói tin tới được máy thì chính là trường hợp này.

Vậy nhận được refused thì biết thêm điều gì? Biết chắc hai điều. Một là gói tin đã tới một máy đang chạy. Hai là trên máy đó không có chương trình nào listen port ấy trên địa chỉ ấy. Trong Bảng 1, đó là hai trường hợp gọi sai địa chỉ, khi gói tin tới một máy khác như chính máy người gọi, và không có chương trình listen đúng địa chỉ.

Timeout: không ai trả lời cả

Kiểu hỏng thứ hai là khi gói SYN đi rồi mà không có gì quay về. Hoặc firewall đã vứt nó lặng lẽ, hoặc không có máy nào mang địa chỉ đó. Lúc ấy bên gọi làm gì? Nó không biết chuyện gì đã xảy ra, nên chỉ làm được một việc là gửi lại SYN, mỗi lần chờ lâu hơn lần trước, cho tới khi hết thời gian chờ do chính nó đặt.

Timeout hết giờ chờ
Triệu chứng mà bên gọi thấy khi chờ quá thời hạn mà không nhận được câu trả lời nào. curl in ra mã lỗi (28); thời hạn chờ của nó đặt bằng tham số --max-time.
  refused: máy sống, không ai listen
  curl ── SYN ──────────────────→ hệ điều hành của máy đích
  curl ←────────────────── RST ── "port này không có ai"
  → có câu trả lời → curl tự kết luận → lỗi (7)

  timeout: không ai trả lời
  curl ── SYN ──────────────────→ 10.255.255.1  (không có máy nào)
  curl ── SYN (gửi lại) ────────→        …
  curl ── SYN (gửi lại) ────────→        …
  … im lặng … hết --max-time → curl bỏ cuộc → lỗi (28)
Hình 10 Hai kịch bản ở tầng TCP. Chỉ kịch bản đầu có một gói tin quay về.

Phân biệt bằng người quyết định dừng, không bằng tốc độ

Phân biệt hai triệu chứng bằng gì? Cách dễ nghĩ tới nhất là theo tốc độ, refused thì nhanh còn timeout thì chậm. Cách đó nghe hợp lý nhưng không đáng tin, và lab cho thấy lý do: trên Windows, một lần refused tới chính máy mình mất hơn hai giây. Tiêu chí vững hơn là hỏi ai quyết định lúc dừng.

  • refused dừng vì bên gọi nhận được câu trả lời. Nó có điểm kết thúc riêng, đặt --max-time bao nhiêu cũng vậy.
  • timeout dừng vì bên gọi tự bảo mình thôi chờ. Thời gian của nó đúng bằng con số bạn đặt, khớp từng mili-giây.
  refused  →  curl dừng vì nhận được câu trả lời
              có điểm kết thúc của riêng nó, không phụ thuộc vào ta

  timeout  →  curl dừng vì ta bảo nó dừng
              kéo dài đúng bằng --max-time: đặt 5 thì 5 giây, đặt 15 thì 15
Hình 11 Phép thử phân biệt hai triệu chứng: thay đổi --max-time rồi xem thời gian của lệnh nào thay đổi theo.

Quy tắc này có ngoại lệ nào không? Có một. Vài firewall được cấu hình để từ chối thay vì vứt, và chúng tự gửi lời từ chối thay cho máy đích, nên bên gọi cũng thấy refused. Vì vậy nói cho chính xác thì phải thêm chữ "thường". timeout thường chỉ về firewall vứt lặng lẽ hoặc một địa chỉ không có máy nào, còn refused thường chỉ về một máy còn sống mà không ai listen port đó.

Đào sâu · RST trong đặc tả TCP

Hành vi "gửi RST khi không có ai listen" được quy định trong đặc tả TCP hiện hành, RFC 9293: khi một gói TCP tới một kết nối không tồn tại, bên nhận trả lời bằng một gói reset. Trong Java, gói reset ấy hiện ra thành java.net.ConnectException: Connection refused, lỗi quen thuộc khi app không kết nối được database.

Khác mạng thì sao: VPN

Mọi thứ từ đầu bài tới giờ đều giả định người gọi ngồi cùng mạng với bạn. Có lần mình chỉ cho một người bạn cách để hai máy cùng mạng gọi được port 8080 của nhau, và bạn ấy hỏi tiếp: khác mạng thì sao? Mình không biết, tra trên mạng thì thấy người ta bảo dùng VPN server. Mục này trả lời câu hỏi đó bằng đúng những thứ bài đã có.

Trước hết, vì sao khác mạng thì không gọi được? Giả sử người bạn đó ngồi ở nhà của họ và gọi 192.168.10.38:8080. Địa chỉ đó thuộc dải mạng riêng, nên gói tin đi ra router nhà họ rồi không router nào trên Internet chịu chuyển tiếp. Không ai trả lời, nên họ thấy timeout. Còn địa chỉ công khai của nhà bạn thì thuộc về router trong nhà bạn, mà router ấy bỏ mọi kết nối do người ngoài tự mở vào. Chuyện router này được kể kỹ ở Mạng và thiết bị mạng.

Có ba lối để người ở mạng khác vào được. Lối thứ nhất là dặn router chuyển một port từ ngoài vào thẳng laptop, gọi là port forwarding, và làm vậy thì app lộ ra cho cả Internet. Lối thứ hai là đưa app lên một server có địa chỉ công khai. Lối thứ ba là VPN, hợp nhất khi chỉ muốn vài người quen vào được máy mình.

VPN mở đường bằng cách nào? Nó thêm cho mỗi máy một card mạng ảo, chính là cái card thứ ba trong Hình 2. Sau khi hai người cài cùng một phần mềm VPN và đăng nhập vào cùng một mạng VPN, laptop bạn có thêm địa chỉ 10.8.0.2, còn máy bạn ấy có thêm 10.8.0.3. Card thật thì đẩy gói tin ra dây hoặc ra sóng. Card ảo không có dây hay sóng, nên gói tin giao cho nó sẽ đi vào phần mềm VPN, vì card ảo chính là phần mềm đó.

Phần mềm VPN làm gì với gói tin ấy thì theo một lần bạn ấy mở http://10.8.0.2:8080 là thấy.

  1. Máy bạn ấy thấy đích 10.8.0.2 cùng mạng với card ảo của nó, nên giao gói tin cho card ảo.
  2. Phần mềm VPN mã hoá gói tin rồi bỏ cả gói vào trong một gói mới. Gói mới ghi nơi nhận là địa chỉ công khai của VPN server, và đi ra bằng card WiFi thật như mọi gói tin lên Internet.
  3. VPN server biết 10.8.0.2 là laptop bạn, vì laptop bạn cũng đang giữ một kết nối tới nó. Server đẩy gói tin vào kết nối đó.
  4. Phần mềm VPN trên laptop bạn bóc gói ngoài, giải mã, rồi đưa gói gốc cho hệ điều hành, y như gói vừa đi vào từ card ảo.
  5. Hết việc của VPN. Hệ điều hành xét ba điều kiện như mọi kết nối khác: firewall có cho qua không, có chương trình nào listen ở 10.8.0.2:8080 không.
 máy bạn ấy                                       laptop bạn
 gọi 10.8.0.2:8080                                ai listen 10.8.0.2:8080?
   │ 1                                              ▲ 5
 card ảo 10.8.0.3                                 card ảo 10.8.0.2
   │ 2 mã hoá, bọc vào gói mới                      ▲ 4 bóc gói ngoài, giải mã
 card WiFi ──► router ──► VPN server ──► router ──► card WiFi
                              3
Hình 12 Năm bước của một gói tin đi qua VPN. Trên Internet chỉ có gói ngoài, gửi tới VPN server. Gói gốc gửi tới 10.8.0.2 nằm bên trong nó.

Trong năm bước có ba thứ mang chữ VPN, và rất dễ gọi lẫn. Hai thứ đầu là phần mềm VPN chạy trên hai máy. Thứ ba là VPN server, một máy ở ngoài Internet có địa chỉ công khai mà hai phần mềm kia cùng gọi tới. Vì cả hai máy đều là bên gọi ra nên router của cả hai nhà đều cho qua, và nhờ vậy VPN đi được qua chỗ mà một kết nối gọi thẳng bị chặn. Vài dịch vụ VPN còn cho hai máy nối thẳng với nhau sau khi server giới thiệu, nhưng ý chính không đổi.

Định nghĩa · VPN

VPN là viết tắt của Virtual Private Network, mạng riêng ảo. RFC 4949 định nghĩa nó là "a restricted-use, logical … computer network that is constructed from the system resources of a relatively public, physical … network (e.g., the Internet)". Tạm dịch là một mạng chỉ dành cho một nhóm, không có thật về phần cứng, dựng trên tài nguyên của một mạng thật và công cộng như Internet.

VPN chỉ lo mang gói tin tới được laptop bạn. Có ai nhận gói tin đó không thì vẫn do listen address quyết định. Mình từng nghĩ khác, rằng bật VPN lên thì app sẽ tự listen ở địa chỉ của VPN. Sai, vì app chọn địa chỉ listen một lần lúc khởi động, còn VPN chỉ thêm cho máy một địa chỉ chứ không đụng gì tới app. Bảng 2 đặt bốn cách listen cạnh ba người gọi.

Bảng 2 Laptop có ba địa chỉ. Ai gọi được là tuỳ app listen ở đâu, coi như firewall đã cho qua.
App listen ởChính laptop gọi 127.0.0.1Máy cùng WiFi gọi 192.168.10.38Máy trong mạng VPN gọi 10.8.0.2
127.0.0.1đượcrefusedrefused
192.168.10.38refusedđượcrefused
10.8.0.2refusedrefusedđược
0.0.0.0đượcđượcđược

Dòng cuối là lý do app Spring Boot để mặc định thì vào được từ cả ba phía. Dòng thứ ba là cách cho riêng người trong mạng VPN vào, bằng cách đặt server.address thành địa chỉ VPN. Mọi ô còn lại đều là refused chứ không phải timeout, vì gói tin tới được máy, máy còn sống, và không ai listen ở địa chỉ đó nên hệ điều hành từ chối ngay. timeout chỉ xuất hiện khi gói tin không tới nơi, ví dụ khi bạn ấy thoát phần mềm VPN rồi vẫn gọi 10.8.0.2.

Các lab dưới đây đổi bốn câu hỏi của bài thành số đo. Lab 1 là output của buổi học ngày 24/09/2026, còn Lab 2 và Lab 3 mình chạy lại trên cùng máy ngày 03/10/2026 khi tách bài. Tất cả chạy trong Git Bash trên Windows, nên con số trên máy bạn sẽ hơi khác.

Lab 1: tự gây refused, rồi tự gây timeout

Làm sao tự tạo ra một lần refused? Gọi port 9999 của chính máy mình. Máy chắc chắn đang sống, nhưng không chương trình nào listen port đó, nên đủ đúng hai điều kiện của refused.

Còn timeout thì tạo bằng cách gọi 10.255.255.1. Địa chỉ này thuộc dải mạng riêng 10.x, và trong mạng gia đình bình thường không có máy nào mang nó. Vì gói SYN không tới máy nào nên không có gì quay về. Đây là cách an toàn để tự tạo một timeout mà không làm phiền máy của ai.

Trong hai lệnh còn ba thứ chưa gặp:

  • -4 buộc curl chỉ dùng IPv4.
  • -o /dev/null bỏ phần nội dung nhận được, vì phép đo này chỉ cần biết lỗi và thời gian.
  • time đo tổng thời gian chạy.
time curl -4 -o /dev/null http://127.0.0.1:9999
time curl -4 -o /dev/null --max-time 5 http://10.255.255.1:9999
127.0.0.1:9999      → curl: (7)  Failed to connect … after 2076 ms     real 2.155s
10.255.255.1:9999   → curl: (28) Connection timed out after 5007 ms    real 5.066s
Hình 13 Output thật của Lab 1 trên máy mình, 24/09/2026, rút gọn mỗi lệnh về một dòng.

Hai con số trong ngoặc nghĩa là gì? Mã (7) là "không kết nối được", còn mã (28) là "hết giờ chờ". Tên chính thức của hai mã này trong thư viện của curl là CURLE_COULDNT_CONNECT và CURLE_OPERATION_TIMEDOUT.

Con số đáng nhìn nhất là 2076 ms của lệnh refused, vì nó chậm hơn nhiều so với lý thuyết "có người trả lời ngay". Mình ghi lại đúng chỗ này: refused tới chính máy mình mà mất hơn 2 giây.

Vì sao Windows mất hai giây cho một lần bị từ chối

Hai giây đó từ đâu ra? Trên Linux, khi nhận gói RST từ chính máy mình, hệ điều hành báo lỗi cho chương trình gần như ngay, thường sau vài mili-giây. Windows làm khác, và nhiều người đã quan sát thấy nó thử lại vài lần rồi mới kết luận kết nối bị từ chối, kể cả khi đích là chính máy mình. Trang này không dẫn được tài liệu chính thức của Microsoft cho con số hai giây, nên hãy coi đó là một quan sát đo được, chưa phải quy định.

Có phải do localhost thử ::1 trước không? Không, vì lệnh trên đã dùng -4 và gõ thẳng 127.0.0.1. Con số 2,155 giây là của Windows.

Lab 2: chứng minh timeout do bạn quyết định

Hình 11 nói timeout dài đúng bằng con số bạn đặt, vậy thử đổi con số đó xem. Chỉ đổi 5 thành 15 rồi chạy lại cả hai lệnh với cùng thời hạn chờ. Nếu Hình 11 đúng thì lệnh timeout phải kéo dài đúng 15 giây, còn lệnh refused vẫn dừng ở khoảng hai giây.

time curl -4 -o /dev/null --max-time 15 http://10.255.255.1:9999
time curl -4 -o /dev/null --max-time 15 http://127.0.0.1:9999
curl: (28) Connection timed out after 15003 milliseconds
real    0m15.059s

curl: (7) Failed to connect to 127.0.0.1 port 9999 after 2017 ms: …
real    0m2.082s
Hình 14 Output thật của Lab 2 trên máy mình, 03/10/2026, cắt bớt đuôi dòng lỗi thứ hai. Timeout khớp --max-time tới từng mili-giây; refused không đổi.

Kết quả khớp dự đoán. Còn nếu trên máy bạn lệnh đầu dừng sớm với No route to host thì mạng của bạn đang chủ động từ chối đường tới dải 10.x. Đó là một lời từ chối chứ không phải im lặng, và thấy vậy thì ghi vào notes.md của bạn.

Lab 3: tự gây lỗi: app listen sai địa chỉ

Lab này dựng lại Hình 6 trên máy bạn. lab/nghe.js là một server nhỏ viết bằng JavaScript, listen port 8080 trên địa chỉ bạn truyền vào. Bạn chạy nó bằng Node, chương trình chạy JavaScript ngoài trình duyệt.

Trước hết tìm địa chỉ của máy trong mạng WiFi bằng lệnh ipconfig của Windows, ở dòng IPv4 Address dưới mục card WiFi. Rồi mở hai cửa sổ Git Bash, đứng ở thư mục của bài.

# Cửa sổ 1: chỉ listen trên loopback
node lab/nghe.js 127.0.0.1

# Cửa sổ 2: thay 192.168.x.y bằng địa chỉ WiFi của máy bạn
netstat -ano | grep ":8080 " | grep LISTEN
curl http://127.0.0.1:8080
curl http://192.168.x.y:8080

Kỳ vọng thấy ba thứ:

  • netstat in ra 127.0.0.1:8080 … LISTENING.
  • Gọi bằng 127.0.0.1 thì nhận xin chao tu 127.0.0.1.
  • Gọi bằng địa chỉ WiFi thì ra curl: (7), là refused.

App có hỏng không? Không, nó vẫn chạy bình thường, chỉ là nó không nhận kết nối gọi tới địa chỉ WiFi. Đây là thiếu điều kiện có chương trình listen đúng địa chỉ, vì có chương trình listen port 8080 nhưng không phải trên địa chỉ được gọi.

Giờ sửa lỗi vừa gây. Bấm Ctrl+C ở cửa sổ 1, chạy lại với node lab/nghe.js 0.0.0.0, rồi lặp lại ba lệnh ở cửa sổ 2. Dòng netstat đổi thành 0.0.0.0:8080, và cả hai lệnh curl đều có câu trả lời.

Bước cuối cần một máy thứ hai hoặc điện thoại cùng WiFi. Từ máy đó mở http://192.168.x.y:8080. Nếu nó quay một lúc rồi báo lỗi thì bạn đang thiếu điều kiện firewall cho qua, vì firewall của Windows vứt lặng lẽ kết nối đi vào.

Thêm luật cho port 8080 bằng lệnh ở tab Firewall rồi thử lại. Xong lab thì gỡ luật đi:

Remove-NetFirewallRule -DisplayName "Spring Boot dev 8080"
Vì sao gọi địa chỉ WiFi từ chính máy mình không thử được firewall

Gói tin từ máy bạn gửi tới chính địa chỉ WiFi của máy bạn thì không thật sự ra mạng. Hệ điều hành chuyển nó ngay trong máy, và firewall thường không coi đó là kết nối đi vào từ bên ngoài.

Nên hai lệnh curl ở cửa sổ 2 chỉ kiểm được app có listen đúng địa chỉ hay không, không nói gì về firewall. Muốn kiểm firewall thì gói tin phải thật sự đến từ một máy khác.

Lab 4 (tự chọn): gọi từ mạng khác qua VPN

Lab này cần một dịch vụ VPN cài trên hai máy nên mình để tự chọn, và mình chưa chạy nó trên máy mình. Máy thứ hai dễ kiếm nhất là điện thoại đã tắt WiFi và dùng mạng di động, vì lúc đó nó ở một mạng khác với laptop.

  1. Cài cùng một phần mềm VPN lên laptop và điện thoại, đăng nhập cùng một tài khoản. Chạy ipconfig trên laptop sẽ thấy thêm một mục cho card ảo, địa chỉ ở mục đó là địa chỉ VPN của laptop.
  2. Chạy node lab/nghe.js 0.0.0.0 trên laptop, rồi từ điện thoại mở http://<địa chỉ VPN>:8080. Kỳ vọng nhận được câu chào của server. Nếu nó quay một lúc rồi báo lỗi thì firewall trên laptop đang vứt kết nối đi vào, thêm luật như ở tab Firewall.
  3. Tự gây lỗi: dừng server, chạy lại với node lab/nghe.js 127.0.0.1, rồi gọi lại từ điện thoại. Kỳ vọng bị từ chối ngay, đúng ô refused ở dòng đầu của Bảng 2.
  4. Chạy lại server với 0.0.0.0, thoát phần mềm VPN trên điện thoại, rồi gọi lại. Kỳ vọng nó quay cho tới khi hết thời hạn chờ, tức timeout.

Tự kiểm tra

Bấm vào từng dòng khi bạn tự giải thích được, bằng lời của mình, mà không nhìn lại tài liệu.

  • Giải thích được vì sao localhost là một từ tương đối
  • Nói được một máy có mấy địa chỉ IP, và vì sao chúng không thay thế được nhau
  • Kể được ba điều kiện để gọi tới được một app, theo đúng thứ tự gói tin gặp chúng
  • Giải thích được listen 127.0.0.1 khác listen 0.0.0.0 thế nào
  • Phân biệt được refused và timeout, và điều kiện nào thiếu sinh ra cái nào
  • Giải thích được vì sao refused chỉ xảy ra khi máy đích còn sống
  • Đọc được bộ bốn của một kết nối trong output curl -v
  • Nói được vì sao một server không đặt trên laptop ở nhà
  • Kể được năm bước của một gói tin đi qua VPN, và vì sao VPN không quyết định app listen ở đâu

Những chỗ mình từng hiểu sai

Mỗi dòng dưới đây là một chỗ mình đã hiểu sai khi học bước mở kết nối. Cách nghĩ nào nghe cũng hợp lý, nhưng sai ở một chỗ. Bạn đang nghĩ giống cột đầu thì link ở cột cuối dẫn về đúng mục đã giảng.

Bảng 3 Những chỗ mình từng hiểu sai về địa chỉ, port, listen, firewall và hai triệu chứng.
Mình từng nghĩThực tếXem lại
127.0.0.1 là địa chỉ IPv4 mặc định của máyĐó là địa chỉ loopback, một trong nhiều địa chỉ của máy, và chỉ gọi tới được từ trong máyMột máy có nhiều địa chỉ
Dải 127.0.0.0/8 có 127⁴ địa chỉ/8 đếm bit: khối có 2²⁴, khoảng 16,7 triệu địa chỉ, dù máy thường chỉ dùng 127.0.0.1Lịch sử địa chỉ IP, tab CIDR
"Chưa mở cổng 8080" là một khái niệmĐó là hai khái niệm khác nhau: có chương trình listen hay không, và firewall có cho qua hay không. Hai thứ cho hai triệu chứng khác nhauBa điều kiện
Server còn sống thì không thể bị refusedNgược lại: refused chỉ xảy ra khi máy còn sống, vì phải có hệ điều hành đang chạy mới gửi được gói RSTRefused
Mở firewall xong là người khác vào đượcChưa chắc. Nếu app chỉ listen 127.0.0.1, triệu chứng chỉ đổi từ timeout sang refused; triệu chứng đổi nghĩa là firewall đã sửa đúngBa điều kiện
Refused thì nhanh, timeout thì chậmTrên Windows, refused cũng mất hơn hai giây. Phân biệt bằng việc ai quyết định lúc dừngNgười quyết định dừng
Bật VPN thì app tự listen ở địa chỉ của VPNApp chọn địa chỉ listen một lần lúc khởi động. VPN chỉ thêm cho máy một địa chỉ, app listen 0.0.0.0 thì nhận luôn ở địa chỉ đó, còn listen địa chỉ khác thì khôngKhác mạng thì sao
Không ai listen thì người gọi thấy timeoutKhông ai listen mà gói tin tới được máy thì hệ điều hành từ chối ngay, tức refused. timeout là khi không có câu trả lời nàoRefused

Kết lại

Câu hỏi của bài là app chạy bình thường khi gọi bằng localhost:8080 trên chính máy mình mà người khác gọi vào thì không được, vậy bước mở kết nối hỏng ở đâu? Nó hỏng ở một trong ba điều kiện. Muốn gọi tới được thì cần đủ cả ba, theo đúng thứ tự gói tin gặp chúng.

  1. Gọi đúng địa chỉ: một địa chỉ của máy đích mà người gọi với tới được, không phải localhost.
  2. Firewall cho qua.
  3. Có chương trình listen đúng địa chỉ: listen port đó trên đúng địa chỉ được gọi tới.

Làm sao biết đang thiếu điều kiện nào? Nhìn triệu chứng. refused là gói tin đã tới một máy đang sống mà không ai listen ở đó. timeout là không có câu trả lời nào, thường vì firewall vứt gói tin lặng lẽ. Người gọi ở mạng khác thì cần thêm một đường tới được máy bạn, ví dụ VPN, rồi ba điều kiện vẫn áp dụng y nguyên.

Câu hỏi còn mở

Đủ ba điều kiện thì bắt tay ba bước thành công, kết nối mở, và refused với timeout biến mất. Nhưng có kết nối chưa có nghĩa là có câu trả lời đúng. Trình duyệt giờ nhận được một trang, mà trang đó có thể là trang lỗi mang một con số. Từ đó còn lại một câu hỏi:

Kết nối tới được rồi mà trang vẫn báo 502, 500 hay 504. Lỗi đó do ai viết ra, và nó cho biết chặng nào đang hỏng?

Nguồn đọc thêm

Chỉ gồm văn bản chuẩn gốc và tài liệu chính thức. Dùng để tra cứu, không cần đọc hết.

  1. RFC 791, Internet Protocol. Định nghĩa IPv4 và địa chỉ 32 bit. rfc-editor.org/rfc/rfc791
  2. RFC 9293, Transmission Control Protocol (TCP). Phần đầu gói TCP, bắt tay ba bước, gói reset. rfc-editor.org/rfc/rfc9293
  3. RFC 1122, Requirements for Internet Hosts. Dải loopback 127.0.0.0/8. rfc-editor.org/rfc/rfc1122
  4. RFC 1918, Address Allocation for Private Internets. Ba dải mạng riêng. rfc-editor.org/rfc/rfc1918
  5. RFC 4291, IP Version 6 Addressing Architecture. ::1 và fe80::/10. rfc-editor.org/rfc/rfc4291
  6. RFC 6761, Special-Use Domain Names. Tên localhost. rfc-editor.org/rfc/rfc6761
  7. RFC 6335, quản lý số hiệu port, gồm khoảng port tạm 49152-65535. rfc-editor.org/rfc/rfc6335
  8. RFC 2131, Dynamic Host Configuration Protocol. Router cho thuê địa chỉ. rfc-editor.org/rfc/rfc2131
  9. RFC 4949, Internet Security Glossary, Version 2 (2007). Định nghĩa VPN. rfc-editor.org/rfc/rfc4949
  10. IANA, sổ đăng ký các dải địa chỉ IPv4 dùng cho mục đích đặc biệt. iana.org/assignments/iana-ipv4-special-registry
  11. curl, danh sách mã lỗi của libcurl, gồm CURLE_COULDNT_CONNECT (7) và CURLE_OPERATION_TIMEDOUT (28). curl.se/libcurl/c/libcurl-errors.html
  12. Microsoft Learn, lệnh netstat. learn.microsoft.com/…/netstat
  13. Microsoft Learn, lệnh PowerShell New-NetFirewallRule, gồm tham số -RemoteAddress. learn.microsoft.com/…/new-netfirewallrule

Nguồn ảnh

Ảnh chụp lấy từ Wikimedia Commons, dùng theo giấy phép ghi ở từng dòng; ảnh đã được chuyển sang đen trắng và cắt khung cho hợp bố cục trang; ảnh chụp đồ vật còn được tách khỏi nền cũ để đặt thẳng lên nền trang. Ảnh chụp màn hình do mình tự chụp; các khung màu là phần đánh dấu thêm vào. Truyện tranh của xkcd và của turnoff.us dùng theo giấy phép phi thương mại ghi ở từng dòng; đã đổi cỡ và chuyển sang đen trắng.

  1. Ảnh 1Networking Problems, Randall Munroe, xkcd.com, CC BY-NC 2.5.
  2. Ảnh 2Card mạng Intel X540-T2, Dmitry Nosachev, Wikimedia Commons, CC BY-SA 4.0.
  3. Ảnh 3Sổ đăng ký không gian địa chỉ IPv4, chụp màn hình iana.org, 30/09/2026; đã ẩn các khối 015/8 tới 123/8 và ba cột WHOIS, RDAP, Note.
  4. Ảnh 4Khuôn phần đầu gói tin TCP, RFC 9293, chụp màn hình rfc-editor.org, 03/10/2026.
  5. Ảnh 5Firewall Netgear FVS336G, mặt trước, Zuzu, Wikimedia Commons, CC BY-SA 3.0.
  6. Ảnh 6Bắt tay ba bước, RFC 9293, Hình 6, chụp màn hình rfc-editor.org, 03/10/2026.
  7. Ảnh 7TCP Buddies, Daniel Stori, turnoff.us, CC BY-NC-SA 4.0.