Публікування домашнього сервера через VPS, коли CGNAT блокує порти
Шлях від браузера до домашньої машини: доведи, що лінія за CGNAT, спрямуй DNS на VPS, зроби DNAT одного порту, полагодь зворотний шлях і перевір ззовні.
Я почав ззовні, як зробив би користувач: ввів ім'я в браузері телефона на мобільних даних і дивився, як з'єднання висить, доки не минув час. Ім'я розв'язувалося, отже DNS був гаразд. Сторінка пробросу портів на домашньому роутері теж була глухим кутом, бо її адреса WAN показувала 100.64.31.42, а це не адреса, на яку провайдер маршрутизує мене з інтернету. Це число пояснює всю несправність: лінія перебуває за carrier-grade NAT, тож публічна адреса з мого боку тунелю належить провайдеру. Далі йде шлях, який я пройшов від тієї завислої вкладки до сервісу, що відповідає.
Почни з клієнта й рухайся всередину
Перш ніж змінювати будь-яке правило, доведи, де обривається ланцюг. На ноутбуці я виконав dig +short files.example.net і отримав адресу VPS, що виключило застарілий DNS на моїй машині. Потім curl -v --connect-timeout 5 files.example.net:5022 вичерпав час замість відмови. Ця різниця важлива: тайм-аут зазвичай означає, що пакети відкидаються по дорозі, а відмова означає, що щось відповіло й сказало ні.
Наступним кроком усередину був сам VPS. ss -ltnp не показав нічого, що слухає на 5022, як і очікувалося до появи правила DNAT. На тій самій машині wg show (або ip -br a show tun0 для OpenVPN) підтвердив, що домашній peer піднятий і має свіжий handshake. Якщо peer не працює, жодне правило NAT не допоможе, а зелене світло на сторінці VPN домашнього роутера нічого не означає.
Підтверди, що домашня лінія справді за CGNAT
Порівняй адресу, яку роутер повідомляє на інтерфейсі WAN, з тим, що бачить зовнішній сервіс. Усе в 100.64.0.0/10, тобто від 100.64.x.x до 100.127.x.x, — це діапазон спільних адрес з RFC 6598, і з інтернету він недосяжний. До того самого висновку доходиш, коли роутер стверджує, що має звичайну приватну адресу, а публічна перевірка повертає щось не пов'язане. Лінія CGNAT може мати й публічну адресу, чинну лише до наступного lease, тож проброс порту начебто працює годину, а потім тихо вмирає.
Деякі провайдери справді видають справжню публічну адресу, але блокують вхідні порти 80 і 443 на домашніх тарифах. Якщо твій сервіс лише веб, а адреса WAN справді публічна, спитай підтримку, перш ніж будувати relay. Якщо ні, схема нижче застосовна однаково до кожного порту, і варто припинити експеримент на боці роутера: транслятор у мережі провайдера завершує з'єднання раніше, ніж воно доходить до твого інтерфейсу WAN.
Спрямуй ім'я на адресу VPS
DNS не пересилає трафік, тому A-запис сервісу має містити публічну адресу VPS. A-запис на 192.168.1.20 розв'язується корисно лише для клієнтів, які вже всередині VPN; з інтернету він розв'язується в адресу, яку ніхто не може маршрутизувати. Для адміністративного доступу до самого VPS я тримаю окреме ім'я, щоб ніколи не плутати, до якого хоста належить запис.
Якщо і VPS, і домашня машина віддають HTTP, дай кожній своє ім'я й дозволь VPS вирішувати, хто відповідає. Звести їх в одне ім'я й сподіватися, що проксі вибере правильний backend, — це саме те, як проводиш цілий день, зневаджуючи не той хост.
Один порт через DNAT, SSH лишається на 22
Прямий варіант — NAT призначення на VPS. Увімкни forwarding для працюючого ядра й тримай у правилі адресу тунелю домашнього сервера, а не його адресу LAN:
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 до самого VPS на 22 і проброси інший порт, наприклад 5022, на домашню машину. Якщо пробросиш 22 на вимкнену машину, втратиш єдиний шлях, який міг би її полагодити. Та сама логіка стосується проксі: тримай порт керування VPS поза правилами relay.
Зроби правила постійними, коли вони запрацюють. У Debian або Ubuntu apt install iptables-persistent і потім netfilter-persistent save записують їх у /etc/iptables/rules.v4. На хості з firewalld еквівалент — постійний проброс порту в зоні, якому перед застосуванням потрібен firewall-cmd --reload. Правила, що живуть лише в пам'яті, зникають під час першого сервісного перезапуску провайдера, зазвичай у невдалу годину.
Полагодь зворотний шлях або уникни потреби в ньому
DNAT переписує лише призначення. Домашній сервер бачить з'єднання з початкової адреси клієнта, і якщо його маршрут за замовчуванням вказує на домашній роутер, відповідь виходить через домашній WAN, де транслятор провайдера її відкидає. Ця асиметрія була другою несправністю, з якою я зіткнувся, після того як правило DNAT виглядало цілком правильним.
Дві правки працюють. Дай домашній машині політичний маршрут, щоб трафік із тунелю повертався через тунель:
ip rule add from 10.8.0.2 table 100
ip route add default dev wg0 table 100
Або дозволь VPS переписати джерело, щоб у відповіді була адреса, якої він може досягти, і дозволь conntrack розвернути її на виході:
iptables -t nat -A POSTROUTING -d 10.8.0.2 -p tcp --dport 5022 -j SNAT --to-source 10.8.0.1
Варіант із SNAT приховує справжню адресу клієнта від домашнього сервера, тож у логах застосунку як peer видно 10.8.0.1. Передай початкову адресу на рівні застосунку або прийми втрату логування за клієнтами, але виріши це свідомо, а не виявляй під час інциденту.
Перевір увесь шлях ззовні
Перевір із мережі, яка не є ні VPS, ні домом: телефон на мобільних даних або дешевий shell в іншому місці. nc -vz files.example.net 5022 має повідомити про успішне з'єднання, а openssl s_client -connect files.example.net:443 -servername files.example.net має показати сертифікат для публічного імені. Потім перевір security group або хмарний фаєрвол провайдера. Правило DNAT, яке ніколи не бачить пакет, зазвичай блокується на рівень вище хоста.
Насамкінець виріши, що станеться, коли тунель упаде. Правило DNAT продовжує приймати з'єднання й тихо спрямовує їх у мертвий тунель, що виглядає точно як зламаний сервіс. Перевірка стану на VPS, яка відхиляє порт, доки peer не працює, коштує кілька рядків і економить годину здогадів. Після цього перестань редагувати правила й поспостерігай добу, перш ніж довіряти.