Hiểu về ip_local_port_range

1. Tổng quan

Khi phát triển ứng dụng backend hoặc triển khai hệ thống phân tán, một trong những lỗi phổ biến mà nhiều kỹ sư gặp phải là:

OSError: [Errno 98] Address already in use

Thông báo trên thường xuất hiện khi một ứng dụng cố gắng mở dịch vụ (LISTEN) trên một cổng đã bị chiếm dụng. Tuy nhiên, trong một số trường hợp, dù không có tiến trình nào rõ ràng đang “LISTEN”, lỗi này vẫn xảy ra. Nguyên nhân sâu xa có thể đến từ một cơ chế của hệ điều hành Linux: ip_local_port_range.

2. ip_local_port_range là gì?

ip_local_port_range là dải cổng (port range) mà hệ điều hành sử dụng để gán ngẫu nhiên cho các kết nối outbound (ví dụ: khi máy A kết nối đến máy B).

Ví dụ:

Khi server thực hiện kết nối TCP outbound:

[ 10.10.10.1:60001 ] ---> [ 10.10.10.2:5432 ]
  • 60001 là cổng tạm thời (ephemeral port) do kernel chọn từ ip_local_port_range
  • 5432 là cổng của PostgreSQL mà máy đích đang lắng nghe

2.1 Cách kiểm tra

cat /proc/sys/net/ipv4/ip_local_port_range

Kết quả:

1025 65000

Tức là Linux sẽ chọn các cổng từ 1025 đến 65000 để làm cổng nguồn cho các kết nối outbound.

3. Minh họa luồng kết nối

┌────────────────────────┐                 ┌────────────────────────────┐
│   APP (Flask, etc.)    │   connect to    │      Database/PostgreSQL   │
│                        │ ─────────────▶  │       10.10.10.2:5432      │
│   10.10.10.1:60001     │                 │                            │
└────────────────────────┘                 └────────────────────────────┘
  • Cổng 60001 được lấy từ ip_local_port_range để tạo socket kết nối ra ngoài
  • Nếu một ứng dụng khác cố tình bind vào cổng 60001, xung đột có thể xảy ra

4. Ví dụ thực tế

4.1 Flask bị lỗi do port bị chiếm

Bạn chạy Flask trên port 60001:

python3 app.py

Lỗi:

OSError: [Errno 98] Address already in use

Bạn kiểm tra:

netstat -tlnp | grep 60001

Không thấy gì. Nhưng khi chạy:

lsof -i :60001

Thấy:

ceph-osd  3013825 ceph  628u  TCP 10.10.10.1:60001->10.10.10.2:6836 (ESTABLISHED)

Tức là cổng 60001 đang được dùng làm outbound TCP socket.

5. Vì sao không nên bind dịch vụ vào các port trong dải này?

5.1 Nguy cơ xung đột

Hệ điều hành có quyền chọn bất kỳ cổng nào trong ip_local_port_range để dùng làm source port. Nếu bạn cố bind dịch vụ vào cổng đó, sẽ dễ gây lỗi.

5.2 Khó debug

Các kết nối outbound không ở trạng thái LISTEN, khiến các công cụ như netstat -tlnp không phát hiện được, dẫn đến hiểu nhầm là port còn trống.

6. Giải pháp

6.1 Tránh dùng cổng trong ip_local_port_range

  • Kiểm tra dải cổng:
cat /proc/sys/net/ipv4/ip_local_port_range
  • Tránh dùng các port trong khoảng này để chạy dịch vụ backend, frontend, proxy, etc.

6.2 Sử dụng cổng ngoài dải ephemeral

Một số cổng gợi ý:

CổngỨng dụng thường dùngGhi chú
80HTTPCần root
443HTTPSCần root
5000FlaskMặc định
8000Django, uvicornRất phổ biến
8080Dev server, reverse proxyThường dùng
8888Jupyter NotebookAn toàn
≥65001Nếu dải ephemeral kết thúc tại 65000Có thể dùng

6.3 Thay đổi dải ephemeral port (nếu cần thiết)

Chỉ thực hiện khi bạn kiểm soát hoàn toàn hệ thống và có lý do chính đáng.

Tạm thời:

echo "20000 40000" > /proc/sys/net/ipv4/ip_local_port_range

Vĩnh viễn:

echo "net.ipv4.ip_local_port_range = 20000 40000" >> /etc/sysctl.conf
sysctl -p

7. Giải pháp tương đương nếu bạn cần chạy nhiều dịch vụ

Giải phápMiễn phíLicenseGhi chú
NGINX reverse proxyBSDGộp nhiều dịch vụ qua 1 port như 80/443
HAProxyGPLCân bằng tải, routing nâng cao
CaddyApacheTự động HTTPS, cấu hình đơn giản
Systemd socket activationGPLChia sẻ socket giữa các service (nâng cao)

8. Lời khuyên

  • Không nên dùng bất kỳ cổng nào trong ip_local_port_range để bind dịch vụ.
  • Luôn kiểm tra dải ephemeral trước khi chọn port cho ứng dụng backend.
  • Nếu bạn cần nhiều cổng, nên dùng reverse proxy để gom về 1 port chuẩn.
  • Tránh hardcode port trùng với cổng của các service phổ biến như Ceph, Docker, Kubernetes…

9. Tài liệu tham khảo

Bài viết gần đây

spot_img

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Đăng ký nhận thông tin bài viết qua email