Dołożenie drugiego klienta OpenVPN na jednym hoście Linux zawodzi w jednym z dwóch miejsc. Albo wewnętrzny klient nie może dotrzeć do własnego serwera, bo zewnętrzny tunel przechwycił ten transport, albo drugi klient przepisuje tabelę tras i pierwszy tunel przestaje działać. Oba problemy leżą w routingu, nie w szyfrowaniu, i oba rozwiązuje się, zanim nowy tunel przeniesie jakikolwiek ruch aplikacji. Adresy tutaj są ilustracyjne: tun0 to tunel zewnętrzny, 203.0.113.9 to serwer wewnętrzny, a TCP 9999 to przepływ, który musi korzystać ze ścieżki wewnętrznej.

Dwa tryby awarii, jedna tabela tras

Wewnętrzny klient otwiera gniazdo UDP do swojego serwera, a to gniazdo podąża za bieżącą tabelą tras jak każde inne. Jeśli zewnętrzny tunel ma już trasę domyślną, pakiety uzgadniania trafiają do tunelu zewnętrznego. Działa to tylko wtedy, gdy zewnętrzny serwer może dotrzeć do serwera wewnętrznego, a druga strona zgadza się przenosić przepływ; w przeciwnym razie uzgadnianie zatrzymuje się i klient próbuje ponownie.

Druga awaria pochodzi z push serwera. Zwykły push to redirect-gateway def1, który instaluje 0.0.0.0/1 i 128.0.0.0/1 zamiast zastępować trasę domyślną. Dwa klienty, z których każdy instaluje własną parę /1, zderzają się więc, a najnowsza para wygrywa dla niemal każdego celu. Szukaj tych dwóch prefiksów w tabeli, gdy tunel ginie po podłączeniu drugiego, bo sama trasa domyślna mogła nigdy się nie zmienić.

Oznacz przepływ i daj mu tabelę

Kieruj wybrany ruch, oznaczając go w tabeli mangle i dopasowując znacznik w regule polityki. Reguła wskazuje dedykowaną tabelę, której trasą domyślną jest urządzenie wewnętrzne, więc tylko oznaczone pakiety idą ścieżką wewnętrzną.

iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200

Większość awarii w tym zestawie reguł wynika z dwóch błędów. Wartość znacznika ustawiana przez firewall i wartość dopasowywana przez regułę muszą być identyczne, bo niezgodność nie daje ani błędu, ani efektu. A PREROUTING widzi tylko pakiety przychodzące z zewnątrz, więc połączenie rozpoczęte na tym hoście wymaga reguły OUTPUT z tym samym dopasowaniem, zanim w ogóle zostanie oznaczone.

Odbierz własność tras drugiemu klientowi

Dwie opcje powstrzymują drugiego klienta przed edycją tabeli i działają w różnych punktach. route-nopull ignoruje wysyłane opcje tras, więc nic nie jest instalowane. route-noexec przyjmuje wysyłane opcje do środowiska klienta, ale zmianę tabeli zostawia hookowi, i tego właśnie chcesz, gdy musisz zobaczyć, co zaproponował serwer.

Hook uruchamia się z wysłanymi wartościami w swoim środowisku i powinien dodawać tylko wpisy, które zatwierdziłeś. Uczyń go idempotentnym: wyczyść tabelę, którą posiada, przed odbudową i nie zakładaj, że poprzednie uruchomienie zostawiło czysty stan. Ponowne połączenie wywołuje hook ponownie, a hook, który dodaje bez czyszczenia, gromadzi zduplikowane trasy.

Wybierz jednego właściciela i zapisz go. Profil, który ustawia obie opcje, albo hook konkurujący ze skryptem ręcznym, tworzy tabelę, której nikt później nie wyjaśni.

Przypnij oba adresy serwerów

Serwer wewnętrzny potrzebuje trasy do hosta przez urządzenie zewnętrzne, zanim wewnętrzny klient się połączy. To ten wpis utrzymuje transport wewnętrzny w tunelu zewnętrznym, zamiast pozwalać mu podążać za trasą domyślną.

ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9

Dodaj także pasującą trasę do hosta dla serwera zewnętrznego w tabeli głównej, inaczej ponowne połączenie może skierować transport samego klienta zewnętrznego do jego własnego tunelu. Kolejność nie jest opcjonalna: dodaj te wpisy po powstaniu odpowiedniego interfejsu, czyli w hooku lub skrypcie uruchamianym po połączeniu. Trasa wskazująca na wyłączone urządzenie jest usuwana lub ignorowana, więc wpis zapisany przy starcie nie przetrwa założonej sekwencji.

Sprawdź wyszukiwanie zamiast czytać całą tabelę. Wynik wyszukiwania trasy podaje urządzenie i adres źródłowy, których użyje kernel, i to jest dokładnie to, co klient otrzymuje, otwierając swoje gniazdo.

Przetestuj łańcuch i uczyń awarię widoczną

Przechwytuj na interfejsie zewnętrznym, gdy wewnętrzny klient się łączy. Wewnętrzne uzgadnianie powinno pojawić się jako zaszyfrowany ładunek w tunelu zewnętrznym; jeśli pojawia się jako zwykły UDP do 203.0.113.9, brakuje trasy do hosta i łańcuch nigdy nie został zbudowany.

Łańcuch, który po cichu wraca do fizycznego uplinku, to najgorszy wynik, bo stan interfejsu nic nie ogłasza, a ruch wewnętrzny wychodzi z niewłaściwego adresu źródłowego. Jeśli drugi klient może połączyć się bezpośrednio, gdy tunel zewnętrzny padnie, wyłącz tę ścieżkę lub przypnij ją do interfejsu zewnętrznego, aby awaria była widoczna.

Zakończ dwoma przepływami i jednym zrzutem reguł: połączenie oznaczone i nieoznaczone powinny iść różnymi ścieżkami, a ip rule show powinno wymienić zamierzony priorytet. Jeśli oba przepływy idą tą samą ścieżką, znacznik nie jest stosowany tam, gdzie myślisz.

Powiązane artykuły