Công bố máy chủ tại nhà qua VPS khi CGNAT chặn cổng
Đường đi từ trình duyệt đến máy trong nhà: chứng minh đường truyền nằm sau CGNAT, trỏ DNS về VPS, làm DNAT một cổng, sửa đường trả về và kiểm tra từ bên ngoài.
Tôi bắt đầu từ bên ngoài, như một người dùng sẽ làm: gõ tên vào trình duyệt điện thoại trên dữ liệu di động và xem kết nối treo cho đến khi hết thời gian. Tên vẫn phân giải được, nên DNS ổn. Trang chuyển tiếp cổng của router ở nhà cũng là ngõ cụt, vì địa chỉ WAN của nó hiển thị 100.64.31.42, và đó không phải địa chỉ mà một nhà cung cấp sẽ định tuyến tới tôi từ internet. Con số đó giải thích toàn bộ sự cố: đường truyền nằm sau carrier-grade NAT, nên địa chỉ công khai ở phía tôi của đường hầm thuộc về nhà cung cấp. Phần tiếp theo là con đường tôi đã đi từ tab bị treo đó đến một dịch vụ biết trả lời.
Bắt đầu từ máy khách và đi vào trong
Trước khi đổi bất kỳ quy tắc nào, hãy chứng minh chuỗi dừng ở đâu. Trên máy tính xách tay tôi chạy dig +short files.example.net và nhận lại địa chỉ VPS, điều này loại trừ DNS cũ trên máy tôi. Sau đó curl -v --connect-timeout 5 files.example.net:5022 hết thời gian thay vì bị từ chối. Khác biệt này quan trọng: hết thời gian thường nghĩa là gói tin bị bỏ dọc đường, còn bị từ chối nghĩa là có thứ gì đó đã trả lời và nói không.
Bước tiếp theo vào trong là chính VPS. ss -ltnp không cho thấy gì đang lắng nghe trên 5022, đúng như dự kiến trước khi có quy tắc DNAT. Trên cùng máy đó wg show (hoặc ip -br a show tun0 với OpenVPN) xác nhận peer ở nhà đang hoạt động và có handshake mới. Nếu peer không chạy, không quy tắc NAT nào giúp được, và đèn xanh trên trang VPN của router ở nhà không có nghĩa gì.
Xác nhận đường truyền tại nhà thực sự nằm sau CGNAT
So sánh địa chỉ router báo trên giao diện WAN với địa chỉ mà một dịch vụ bên ngoài nhìn thấy. Mọi thứ trong 100.64.0.0/10, tức từ 100.64.x.x đến 100.127.x.x, là dải địa chỉ chia sẻ của RFC 6598 và không thể tiếp cận từ internet. Bạn đi đến cùng kết luận khi router khẳng định có địa chỉ riêng thông thường còn một lần tra cứu công khai trả về thứ không liên quan. Đường CGNAT cũng có thể đi kèm địa chỉ công khai chỉ tồn tại đến lần thuê tiếp theo, nên chuyển tiếp cổng có vẻ chạy được một giờ rồi chết lặng.
Một số nhà cung cấp thực sự cấp địa chỉ công khai thật nhưng chặn cổng vào 80 và 443 trên tài khoản dân dụng. Nếu dịch vụ của bạn chỉ là web và địa chỉ WAN thực sự công khai, hãy hỏi bộ phận hỗ trợ trước khi dựng relay. Nếu không, thiết kế dưới đây áp dụng như nhau cho mọi cổng, và đáng để dừng thử nghiệm ở phía router: bộ phiên dịch trong mạng nhà cung cấp kết thúc kết nối trước khi nó chạm tới giao diện WAN của bạn.
Trỏ tên về địa chỉ VPS
DNS không chuyển tiếp lưu lượng, nên bản ghi A của dịch vụ phải chứa địa chỉ công khai của VPS. Một bản ghi A trỏ tới 192.168.1.20 chỉ phân giải hữu ích cho máy khách đã ở trong VPN; từ internet nó phân giải thành địa chỉ không ai định tuyến được. Với quyền truy cập quản trị của chính VPS, tôi giữ một tên riêng, để không bao giờ nhầm bản ghi đang trỏ tới máy chủ nào.
Nếu cả VPS và máy ở nhà đều phục vụ HTTP, hãy cho mỗi bên một tên riêng và để VPS quyết định ai trả lời. Gộp chúng vào một tên và hy vọng proxy chọn đúng backend chính là cách khiến bạn dành cả buổi chiều gỡ lỗi nhầm máy chủ.
DNAT một cổng, để SSH ở 22
Lựa chọn trực tiếp là NAT đích trên VPS. Bật forwarding cho nhân đang chạy và giữ địa chỉ đường hầm của máy chủ ở nhà trong quy tắc, không phải địa chỉ LAN của nó:
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp -d 198.51.100.10 --dport 5022 -j DNAT --to-destination 10.8.0.2
Để SSH tới chính VPS ở cổng 22 và chuyển tiếp một cổng khác, ví dụ 5022, tới máy ở nhà. Nếu chuyển tiếp 22 tới một máy đã tắt, bạn mất con đường duy nhất có thể sửa nó. Cùng logic đó áp dụng cho proxy: giữ cổng quản trị của VPS ngoài các quy tắc relay.
Hãy làm cho các quy tắc tồn tại lâu dài khi chúng chạy được. Trên Debian hoặc Ubuntu, apt install iptables-persistent và sau đó netfilter-persistent save ghi chúng vào /etc/iptables/rules.v4. Trên máy chủ dùng firewalld, tương đương là chuyển tiếp cổng vĩnh viễn trên vùng, cần firewall-cmd --reload trước khi có hiệu lực. Những quy tắc chỉ sống trong bộ nhớ sẽ biến mất ở lần khởi động bảo trì đầu tiên của nhà cung cấp, thường vào giờ không đẹp.
Sửa đường trả về, hoặc tránh nhu cầu phải có nó
DNAT chỉ ghi lại đích. Máy chủ ở nhà thấy một kết nối từ địa chỉ máy khách ban đầu, và nếu tuyến mặc định của nó trỏ về router ở nhà, câu trả lời đi ra qua WAN của nhà, nơi bộ phiên dịch của nhà cung cấp bỏ nó đi. Sự bất đối xứng đó là lỗi thứ hai tôi gặp, sau khi quy tắc DNAT trông hoàn toàn đúng.
Hai cách sửa đều hiệu quả. Cho máy ở nhà một tuyến chính sách để lưu lượng từ đường hầm quay lại qua đường hầm:
ip rule add from 10.8.0.2 table 100
ip route add default dev wg0 table 100
Hoặc để VPS ghi lại nguồn để câu trả lời có địa chỉ mà nó với tới được, và để conntrack đảo ngược nó khi đi ra:
iptables -t nat -A POSTROUTING -d 10.8.0.2 -p tcp --dport 5022 -j SNAT --to-source 10.8.0.1
Bản SNAT giấu địa chỉ máy khách thật khỏi máy chủ ở nhà, nên nhật ký ứng dụng hiển thị 10.8.0.1 là peer. Hãy chuyển địa chỉ gốc ở tầng ứng dụng, hoặc chấp nhận mất ghi nhật ký theo từng máy khách, nhưng hãy quyết định có ý thức thay vì phát hiện ra trong một sự cố.
Kiểm tra toàn bộ đường đi từ bên ngoài
Kiểm tra từ một mạng không phải VPS và cũng không phải nhà: điện thoại trên dữ liệu di động, hoặc một shell rẻ ở nơi khác. nc -vz files.example.net 5022 phải báo kết nối thành công, và openssl s_client -connect files.example.net:443 -servername files.example.net phải hiển thị chứng chỉ cho tên công khai. Sau đó kiểm tra security group hoặc tường lửa đám mây của nhà cung cấp. Một quy tắc DNAT không bao giờ thấy gói tin thường bị chặn ở một lớp phía trên máy chủ.
Cuối cùng, hãy quyết định điều gì xảy ra khi đường hầm sập. Quy tắc DNAT vẫn tiếp tục nhận kết nối và lặng lẽ đẩy chúng vào một đường hầm đã chết, trông đúng như một dịch vụ hỏng. Một kiểm tra sức khỏe trên VPS từ chối cổng chừng nào peer chưa chạy chỉ tốn vài dòng và tiết kiệm một giờ đoán mò. Sau đó hãy ngừng sửa quy tắc và quan sát nó một ngày trước khi tin tưởng.