Zgadza się, dzisiaj powiemy to samo bogu szyfrowania.
Tutaj będziemy mówić o niezaszyfrowanym tunelu IPv4, ale nie o tunelu z „ciepłą lampą”, ale o nowoczesnym tunelu „LED”. I tu też flashują surowe gniazda, i trwają prace z pakietami w przestrzeni użytkownika.
Istnieje N protokołów tunelowania dla każdego smaku i koloru:
Ale jestem programistą, więc zwiększę N tylko o ułamek, a rozwój prawdziwych protokołów pozostawię programistom Kommersant.
W jednym nienarodzonym projektTeraz zajmuję się docieraniem z zewnątrz do hostów korzystających z NAT. Wykorzystując do tego protokoły z kryptografią dla dorosłych, nie mogłem oprzeć się wrażeniu, że to było jak strzelanie do wróbli z armaty. Ponieważ tunel jest używany w większości tylko do robienia dziur w NAT-e, ruch wewnętrzny jest zwykle również szyfrowany, ale nadal tonie w HTTPS.
Badając różne protokoły tunelowania, uwagę mojego wewnętrznego perfekcjonisty raz po raz zwracano na protokół IPIP ze względu na jego minimalny narzut. Ale ma półtora istotne wady dla moich zadań:
wymaga publicznych adresów IP po obu stronach,
i nie ma żadnego uwierzytelnienia dla Ciebie.
Dlatego perfekcjonista został zepchnięty z powrotem w ciemny kąt czaszki lub gdziekolwiek tam siedzi.
A potem pewnego dnia, czytając artykuły na temat natywnie obsługiwane tunele w Linuksie natknąłem się na FOU (Foo-over-UDP), czyli tzw. cokolwiek, zapakowane w UDP. Jak dotąd obsługiwane są tylko protokoły IPIP i GUE (Generic UDP Encapsulation).
„Oto złoty środek! Mi wystarczy prosty IPIP.” - Myślałem.
W rzeczywistości kula okazała się nie całkowicie srebrna. Enkapsulacja w UDP rozwiązuje pierwszy problem - z klientami za NATem można się łączyć z zewnątrz za pomocą wcześniej ustalonego połączenia, ale tutaj połowa kolejnej wady IPIP kwitnie w nowym świetle - każdy z sieci prywatnej może ukryć się za widzialnym publiczny adres IP i port klienta (w czystym IPIP ten problem nie występuje).
Aby rozwiązać ten półtora problemu, narodziło się narzędzie ipipou. Implementuje domowy mechanizm uwierzytelniania zdalnego hosta, bez zakłócania pracy jądra FOU, który szybko i sprawnie będzie przetwarzał pakiety w przestrzeni jądra.
Nie potrzebujemy Twojego scenariusza!
Ok, jeśli znasz publiczny port i adres IP klienta (na przykład wszyscy za nim nigdzie nie idą, NAT próbuje mapować porty 1 w 1), możesz utworzyć tunel IPIP-over-FOU za pomocą następujące polecenia, bez żadnych skryptów.
na serwerze:
# Подгрузить модуль ядра FOU
modprobe fou
# Создать IPIP туннель с инкапсуляцией в FOU.
# Модуль ipip подгрузится автоматически.
ip link add name ipipou0 type ipip
remote 198.51.100.2 local 203.0.113.1
encap fou encap-sport 10000 encap-dport 20001
mode ipip dev eth0
# Добавить порт на котором будет слушать FOU для этого туннеля
ip fou add port 10000 ipproto 4 local 203.0.113.1 dev eth0
# Назначить IP адрес туннелю
ip address add 172.28.0.0 peer 172.28.0.1 dev ipipou0
# Поднять туннель
ip link set ipipou0 up
na kliencie:
modprobe fou
ip link add name ipipou1 type ipip
remote 203.0.113.1 local 192.168.0.2
encap fou encap-sport 10001 encap-dport 10000 encap-csum
mode ipip dev eth0
# Опции local, peer, peer_port, dev могут не поддерживаться старыми ядрами, можно их опустить.
# peer и peer_port используются для создания соединения сразу при создании FOU-listener-а.
ip fou add port 10001 ipproto 4 local 192.168.0.2 peer 203.0.113.1 peer_port 10000 dev eth0
ip address add 172.28.0.1 peer 172.28.0.0 dev ipipou1
ip link set ipipou1 up
gdzie
ipipou* — nazwa lokalnego interfejsu sieciowego tunelu
203.0.113.1 — publiczny serwer IP
198.51.100.2 — publiczny adres IP klienta
192.168.0.2 — adres IP klienta przypisany do interfejsu eth0
10001 — lokalny port klienta dla FOU
20001 — publiczny port klienta dla FOU
10000 — publiczny port serwera dla FOU
encap-csum — możliwość dodania sumy kontrolnej UDP do kapsułkowanych pakietów UDP; można zastąpić noencap-csumnie wspominając, że integralność jest już kontrolowana przez zewnętrzną warstwę enkapsulacji (kiedy pakiet znajduje się wewnątrz tunelu)
eth0 — interfejs lokalny, z którym będzie powiązany tunel ipip
172.28.0.1 — IP interfejsu tunelu klienta (prywatny)
172.28.0.0 — Interfejs serwera tunelu IP (prywatny)
Dopóki połączenie UDP będzie aktywne, tunel będzie działał, ale jeśli się zepsuje, będziesz miał szczęście - jeśli adres IP klienta: port pozostanie taki sam - będzie działał, jeśli się zmienią - zepsuje się.
Najprostszym sposobem, aby wszystko odwrócić, jest wyładowanie modułów jądra: modprobe -r fou ipip
Nawet jeśli uwierzytelnianie nie jest wymagane, publiczny adres IP i port klienta nie zawsze są znane i często są nieprzewidywalne lub zmienne (w zależności od typu NAT). Jeśli pominiesz encap-dport po stronie serwera tunel nie będzie działał, nie jest wystarczająco inteligentny, aby zająć port połączenia zdalnego. W tym przypadku ipipou może również pomóc lub WireGuard i inne podobne mogą Ci pomóc.
Jak to działa?
Klient (zwykle znajdujący się za NATem) otwiera tunel (jak w powyższym przykładzie) i wysyła pakiet uwierzytelniający do serwera, aby ten skonfigurował tunel po swojej stronie. W zależności od ustawień może to być pusty pakiet (tylko po to, aby serwer mógł zobaczyć publiczny adres IP: port połączenia) lub z danymi, dzięki którym serwer może zidentyfikować klienta. Dane mogą mieć postać prostego hasła zapisanego w postaci zwykłego tekstu (przychodzi na myśl analogia z HTTP Basic Auth) lub specjalnie zaprojektowanych danych podpisanych kluczem prywatnym (podobnie jak HTTP Digest Auth tylko mocniej, patrz funkcja client_auth w kodzie).
Na serwerze (strona z publicznym adresem IP) po uruchomieniu ipipou tworzy procedurę obsługi kolejki nfqueue i konfiguruje netfilter tak, aby niezbędne pakiety były wysyłane tam, gdzie powinny być: pakiety inicjujące połączenie z kolejką nfqueue i [prawie] cała reszta trafia bezpośrednio do FOU słuchacza.
Dla niewtajemniczonych, nfqueue (lub NetfilterQueue) to specjalna rzecz dla amatorów, którzy nie wiedzą, jak tworzyć moduły jądra, które za pomocą netfilter (nftables/iptables) pozwalają przekierowywać pakiety sieciowe do przestrzeni użytkownika i tam je przetwarzać za pomocą prymitywne środki pod ręką: zmodyfikuj (opcjonalnie) i oddaj je jądru lub odrzuć.
Dla niektórych języków programowania istnieją powiązania do pracy z nfqueue, dla basha nie było żadnych (heh, nic dziwnego), musiałem użyć Pythona: ipipou używa Kolejka Netfilter.
Jeśli wydajność nie jest krytyczna, za pomocą tej rzeczy możesz stosunkowo szybko i łatwo wymyślić własną logikę pracy z pakietami na dość niskim poziomie, na przykład stworzyć eksperymentalne protokoły przesyłania danych lub trollować lokalne i zdalne usługi za pomocą niestandardowego zachowania.
Surowe gniazda współpracują z nfqueue, np. gdy tunel jest już skonfigurowany i FOU nasłuchuje na żądanym porcie, nie będziesz mógł w zwykły sposób wysłać pakietu z tego samego portu - jest zajęty, ale możesz pobrać i wysłać losowo wygenerowany pakiet bezpośrednio do interfejsu sieciowego za pomocą surowego gniazda, chociaż wygenerowanie takiego pakietu będzie wymagało nieco więcej majsterkowania. Tak powstają pakiety z uwierzytelnianiem w ipipou.
Ponieważ ipipou przetwarza tylko pierwsze pakiety z połączenia (oraz te, którym udało się przedostać do kolejki przed nawiązaniem połączenia), wydajność prawie nie spada.
Gdy tylko serwer ipipou odbierze uwierzytelniony pakiet, tworzony jest tunel i wszystkie kolejne pakiety w połączeniu są już przetwarzane przez jądro z pominięciem nfqueue. Jeżeli połączenie się nie powiedzie, to do kolejki nfqueue zostanie wysłany pierwszy pakiet następnego, w zależności od ustawień, jeśli nie jest to pakiet z uwierzytelnianiem, ale z ostatniego zapamiętanego adresu IP i portu klienta, można go albo przekazać włączone lub wyrzucone. Jeśli uwierzytelniony pakiet pochodzi z nowego adresu IP i nowego portu, tunel jest ponownie konfigurowany tak, aby z nich korzystał.
Zwykły IPIP-over-FOU ma jeszcze jeden problem podczas pracy z NAT - nie da się utworzyć dwóch tuneli IPIP enkapsulowanych w UDP z tym samym IP, ponieważ moduły FOU i IPIP są od siebie dość odizolowane. Te. para klientów korzystających z tego samego publicznego adresu IP nie będzie mogła w ten sposób jednocześnie połączyć się z tym samym serwerem. W przyszłości, być może, zostanie to rozwiązane na poziomie jądra, ale nie jest to pewne. W międzyczasie problemy z NAT można rozwiązać za pomocą NAT - jeśli zdarzy się, że para adresów IP jest już zajęta przez inny tunel, ipipou wykona NAT z publicznego na alternatywny prywatny adres IP, voila! - możesz tworzyć tunele aż do wyczerpania się portów.
Ponieważ Nie wszystkie pakiety w połączeniu są podpisane, wówczas ta prosta ochrona jest podatna na MITM, więc jeśli na ścieżce między klientem a serwerem czai się złoczyńca, który może podsłuchiwać ruch i nim manipulować, może przekierować uwierzytelnione pakiety przez inny adres i utwórz tunel z niezaufanego hosta.
Jeśli ktoś ma pomysł, jak rozwiązać ten problem, pozostawiając większość ruchu w rdzeniu, nie wahaj się zabrać głosu.
Nawiasem mówiąc, enkapsulacja w UDP sprawdziła się bardzo dobrze. W porównaniu z enkapsulacją przez IP jest ona znacznie stabilniejsza i często szybsza pomimo dodatkowego obciążenia nagłówka UDP. Wynika to z faktu, że większość hostów w Internecie działa dobrze tylko z trzema najpopularniejszymi protokołami: TCP, UDP, ICMP. Część materialna może całkowicie odrzucić wszystko inne lub przetwarzać ją wolniej, ponieważ jest zoptymalizowana tylko pod kątem tych trzech.
Na przykład dlatego QUICK, na którym opiera się HTTP/3, został stworzony na bazie UDP, a nie na IP.
No cóż, dość słów, czas zobaczyć, jak to działa w „realu”.
Bitwa
Używany do emulacji prawdziwego świata iperf3. Pod względem stopnia zbliżenia do rzeczywistości jest to mniej więcej tyle samo, co emulowanie prawdziwego świata w Minecrafcie, ale na razie wystarczy.
Uczestnicy konkursu:
referencyjny główny kanał
bohaterem tego artykułu jest ipipou
OpenVPN z uwierzytelnianiem, ale bez szyfrowania
OpenVPN w trybie all-inclusive
WireGuard bez PresharedKey, z MTU = 1440 (tylko dla IPv4)
Dane techniczne dla maniaków Metryki są pobierane za pomocą następujących poleceń:
na kliencie:
UDP
CPULOG=NAME.udp.cpu.log; sar 10 6 >"$CPULOG" & iperf3 -c SERVER_IP -4 -t 60 -f m -i 10 -B LOCAL_IP -P 2 -u -b 12M; tail -1 "$CPULOG"
# Где "-b 12M" это пропускная способность основного канала, делённая на число потоков "-P", чтобы лишние пакеты не плодить и не портить производительность.