Маршрутизація трафіку хоста через VPN у віртуальній машині: шість перевірок
Шість послідовних перевірок, щоб перетворити віртуальну машину на VPN-шлюз, через який маршрутизує трафік її власний хост: режим адаптера, пересилання, NAT, маршрути хоста, DNS, MTU.
Шість перевірок, виконаних по порядку, вирішують, чи може віртуальна машина бути VPN-шлюзом для власного хоста. Гіпервізор нічого цього не робить за вас: тунель належить гостю, маршрут належить хосту, а мережа між ними — звичайна маршрутизація. Я тримаю цей список тому, що пропуск одного кроку завжди дає той самий симптом, тишу, коли хост весело пінгує гостя і не дістається нічого внутрішнього.
Почніть із причини. Або корпоративний клієнт встановлюється лише всередині гостя, або політика тримає його там, а вам усе одно потрібен внутрішній build-сервер з хоста. Якщо хосту дозволено власний клієнт, зробіть саме це: один тунель на хості завжди кращий за дві мережі плюс NAT.
Перевірка 1: хосту потрібен зв'язок, якому він може дати адресу
Хост повинен мати інтерфейс у тому самому префіксі, що й другий адаптер гостя. Host-only у VirtualBox, host-only або користувацький VMnet у VMware Workstation, внутрішній міст або пристрій tap у QEMU/KVM і комутатор Internal у Hyper-V — усі підходять. Гість 192.168.56.2, хост 192.168.56.1, префікс 192.168.56.0/24 — це схема, яку я використовую в лабораторіях.
Мережа QEMU в користувацькому режимі (-netdev user, типовий бекенд SLIRP) і NAT у VirtualBox повністю провалюють цю перевірку. Пакети гостя доходять до проксі-процесу на хості, тому хост ніколи не бачить пристрою з адресами гостя і не має на що спрямувати маршрут. Default Switch у Hyper-V — та сама історія. Якщо ping 192.168.56.2 з хоста не проходить, зупиніться: жоден доданий згодом маршрут це не виправить.
Перевірка 2: гість пересилає і транслює
Пакети з хоста надходять з адресою джерела з мережі host-only і внутрішнім призначенням, тож гість має їх переслати, а потім сховати за адресою свого тунелю. На Linux-гості це означає пересилання, пару правил FORWARD accept і source NAT у напрямку тунелю:
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -A FORWARD -i eth1 -o tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 192.168.56.0/24 -o tun0 -j MASQUERADE
Правило MASQUERADE — це та частина, яку зазвичай забувають. Корпоративні маршрутизатори рідко знають, що 192.168.56.0/24 існує; з правилом внутрішні сервери бачать адресу тунелю гостя і відповідають на неї, а відповідь знаходить шлях назад до гостя.
Windows-гість незграбніший. IPEnableRouter=1 у розділі SYSTEM\CurrentControlSet\Services\Tcpip\Parameters вмикає пересилання без трансляції, тому працює лише там, де корпоративна маршрутизація вже вказує назад на ваш префікс host-only. Internet Connection Sharing транслює, але перенумеровує спільний адаптер у 192.168.137.1, тож прочитайте адресу, яку він фактично зайняв, перш ніж писати маршрути хоста.
Перевірка 3: маршрути хоста називають гостя, а не тунель
Пропускайте через гостя лише ті префікси, які потрібні хосту. У Linux одного маршруту на префікс достатньо, і він чисто відкочується:
sudo ip route add 10.20.0.0/16 via 192.168.56.2
ip route get 10.20.30.40
У Windows еквівалент — route add 10.20.0.0 mask 255.255.0.0 192.168.56.2. Залишайте -p вимкненим, поки тестуєте, щоб поганий маршрут помирав під час перезавантаження, і додайте його, коли шлях запрацює. Не видаляйте маршрут за замовчуванням хоста, якщо тільки всі застосунки на хості не повинні використовувати тунель; якщо повний тунель справді потрібен, додайте два маршрути /1 і збережіть прямий маршрут до мережі гіпервізора:
sudo ip route add 0.0.0.0/1 via 192.168.56.2
sudo ip route add 128.0.0.0/1 via 192.168.56.2
На Windows-хості перевірте також метрику інтерфейсу. Маршрут, доданий, поки адаптер host-only був вимкнений, залишається в таблиці, але ніколи не несе трафік, а другий адаптер із меншою метрикою може виграти пошук для того самого префікса.
Перевірка 4: DNS іде за маршрутом, а не за типовим тунелем
Маршрути рухають пакети; розв'язувач вирішує, які імена створюють пакети. Обмежте корпоративний розв'язувач корпоративною зоною замість заміни розв'язувача хоста і спрямуйте його на DNS-сервер, до якого новий маршрут уже доходить. З systemd-resolved на хості:
resolvectl dns eth1 10.20.0.53
resolvectl domain eth1 '~corp.example.com'
У Windows запис у таблиці політики розв'язання імен робить те саме: Add-DnsClientNrptRule -Namespace ".corp.example.com" -NameServers 10.20.0.53. Якщо гість запускає власний розв'язувач на адресі host-only, використовуйте цю адресу, але лише після того, як переконаєтеся, що він відповідає на 192.168.56.2, а не на loopback.
Потім перевірте MTU, перш ніж звинувачувати DNS у повільних сторінках. Інтерфейс тунелю менший за 1500-байтовий інтерфейс хоста, тому хост, який надсилає сегменти повного розміру з увімкненим бітом DF, бачить, як їх відкидають десь усередині гостя. Класичний симптом — робочий ping і внутрішній веб-застосунок, що зависає після рукостискання TLS. Затисніть MSS на шляху пересилання в гості:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
Перевірка 5: перевірте з обох боків
З хоста traceroute або tracert до внутрішньої адреси мають показувати 192.168.56.2 як перший хоп, а не фізичний шлюз. У гості дивіться, як той самий запит виходить: tcpdump -ni any host 10.20.30.40 показує пересланий запит на боці host-only і трансльований на тунелі. Запит, який виходить, повертається і все одно залишає хост мовчазним, вказує на зворотний шлях.
Дві помилки пояснюють більшу частину цього. Або тунель гостя не несе зворотного маршруту для префікса host-only, бо немає правила NAT, або маршрут хоста вказує на адресу, якої гість уже не має після перезавантаження. Перечитайте адреси за допомогою ip -br addr і ip route get замість того, щоб довіряти нотаткам із минулого сеансу.
Перевірка 6: зробіть так, щоб це пережило перезавантаження й оновлення гіпервізора
Збережіть обидві половини: конфігурацію маршрутів netplan або NetworkManager на хості, route add ... -p у Windows і net.ipv4.ip_forward=1 у /etc/sysctl.d/ плюс набір правил брандмауера, який перезавантажується в гості. Зробіть snapshot гостя, щойно конфігурацію підтверджено, до приходу наступного оновлення корпоративного клієнта.
Мережі host-only перенумеровуються, коли гіпервізор заново створює свої адаптери, і це тихо знецінює кожен маршрут і правило NAT, побудовані на 192.168.56.0/24. Після оновлення повторіть перші три перевірки по порядку; це займає хвилину і ловить випадок, коли хост ще має свій маршрут, а гість переїхав у нову підмережу.
IPv6 потребує окремого проходу. Правила ip6tables, записи AAAA і друге сімейство маршрутів хоста не йдуть за налаштуванням IPv4, а корпоративна послуга, доступна через IPv6, виглядатиме вічно зламаною, поки IPv4 працює.
Пройдено чи ні, по порядку: хост може пінгувати адресу гостя в мережі host-only; гість пересилає і робить MASQUERADE в бік тунелю; ip route get повертає гостя як шлюз для внутрішнього префікса; корпоративні імена розв'язуються через обмежений розв'язувач; traceroute показує гостя першим; і ті самі шість відповідей переживають перезавантаження.