SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor

Tailscale: dlaczego połączenie jest wolne?

Niska wydajność Tailscale wynika z użycia serwerów DERP zamiast połączeń bezpośrednich. Sprawdź status komendą tailscale status i napraw blokady UDP oraz NAT na VPS.

Dlaczego Tailscale działa wolno: połączenia przekaźnikowe zamiast bezpośrednich

Tailscale działa wolno, gdy połączenie jest przekazywane przez jeden ze współdzielonych serwerów DERP, natomiast osiąga prędkość zbliżoną do łącza, gdy połączenie jest bezpośrednie. Połączenie bezpośrednie przesyła zaszyfrowane pakiety WireGuard prosto między maszynami, więc jego wydajność zależy od przepustowości obu łączy internetowych. Połączenie przekaźnikowe wysyła każdy pakiet najpierw przez trzecią maszynę, więc dziedziczy opóźnienia tego urządzenia oraz przydzieloną część jego przepustowości. Koszt tego rozwiązania zależy od przekaźnika. Tailscale określa swoje serwery DERP jako „zazwyczaj wolniejsze od połączeń bezpośrednich”, podczas gdy własny przekaźnik jest „zazwyczaj szybszy niż DERP” i we wczesnych testach Tailscale osiągał przepustowość „zbliżoną do połączenia bezpośredniego”. Strona wydajności Tailscale podaje ogólną zasadę w jednym zdaniu: „Połączenia bezpośrednie niemal zawsze skutkują niższymi opóźnieniami i wyższą przepustowością”.

Nic wewnątrz aplikacji nie wskazuje na różnicę. Kopiowanie plików jest po prostu wolne, a sesja SSH ma opóźnienia. Pierwszym zadaniem jest więc ustalenie, jaki rodzaj połączenia jest aktualnie używany. Dwie komendy odpowiadają na to pytanie w mniej niż minutę, a wszystko, co następuje później, to usuwanie przyczyny problemu. Warto poznać zasady konfiguracji przed rozpoczęciem, ponieważ serwer koordynujący i płaszczyzna danych WireGuard to oddzielne systemy i tylko płaszczyzna danych przesyła bajty.

Dwie komendy rozróżniające połączenie bezpośrednie od przekaźnikowego

Przed wykonaniem jakichkolwiek pomiarów wyślij ruch do węzła. Tailscale buduje ścieżkę na żądanie, więc węzeł, z którym dzisiaj nie nawiązano kontaktu, może nie mieć jeszcze wynegocjowanego połączenia, co skutkowałoby odczytem nieaktualnych danych. Wystarczy jeden ping lub jeden curl na adres tailnet węzła docelowego.

tailscale status

Wynik znajduje się na końcu linii każdego węzła.

100.113.160.82 device-a  tagged-devices linux   active; offers exit node; direct 203.0.113.9:41641
100.104.93.78  device-b  you@           android active; relay "tor"

direct wraz z adresem i portem oznacza, że pakiety są przesyłane bezpośrednio pod ten adres. relay "tor" wskazuje serwer DERP (designated encrypted relay for packets), czyli jeden z serwerów przekaźnikowych Tailscale, przez który przechodzi każdy pakiet do danego węzła. Trzecia wartość, peer-relay, została omówiona w kolejnej sekcji.

tailscale ping device-b

Prawidłowe połączenie zaczyna się jako przekaźnikowe, a następnie zmienia stan. Pierwsze pakiety przechodzą przez najbliższy serwer DERP podczas negocjacji między maszynami, po czym ścieżka ulega zmianie:

pong from device-b (100.113.160.82) via DERP(tor) in 51ms
pong from device-b (100.113.160.82) via DERP(tor) in 48ms
pong from device-b (100.113.160.82) via 203.0.113.9:41641 in 35ms

Działanie kończy się w tym miejscu, ponieważ --until-direct domyślnie ma wartość true. Połączenie, które nie może zostać nawiązane bezpośrednio, wygląda inaczej i kończy się komunikatem tekstowym zamiast odpowiedzi pong:

pong from device-b (100.104.93.78) via DERP(tor) in 53ms
pong from device-b (100.104.93.78) via DERP(tor) in 60ms
direct connection not established

Ostatnia linia zawiera kluczową informację. Oznacza ona, że Tailscale wysłał wszystkie zaplanowane sondy i nie uzyskał bezpośredniej ścieżki. Aby monitorować ścieżkę przekaźnikową zamiast zatrzymywać się na pierwszej bezpośredniej, należy uruchomić tailscale ping --until-direct=false -c 20 device-b i przeanalizować rozrzut opóźnień. Ścieżka przekaźnikowa zazwyczaj wykazuje wyższe wartości oraz większą zmienność, ponieważ składa się z dwóch ścieżek internetowych połączonych w punkcie, nad którym użytkownik nie ma kontroli.

Co oznacza peer-relay w statusie Tailscale?

Peer relay to maszyna wewnątrz własnej sieci tailnet, która przekazuje ruch dla innych członków, gdy nawiązanie bezpośredniego połączenia jest niemożliwe. Nasłuchuje ona na wybranym porcie UDP, a demon używa jej w pierwszej kolejności przed węzłami DERP. tailscale status oznacza takie połączenie jako peer-relay, a tailscale ping wyświetla punkt końcowy przekaźnika:

pong from device-b (100.97.143.93) via peer-relay(203.0.113.42:40000:vni:1) in 4ms
direct connection not established

Należy zwrócić uwagę na ten szczegół. Nadal nie jest to połączenie bezpośrednie, więc działanie kończy się statusem direct connection not established. Zmienił się jedynie podmiot przekazujący ruch. Serwer VPS z publicznym adresem IP i dużym limitem transferu jest znacznie lepszym przekaźnikiem dla własnego ruchu niż współdzielony węzeł DERP, dlatego jest to istotne dla osób wynajmujących serwery. Funkcję tę należy włączyć na maszynie posiadającej stabilny publiczny punkt końcowy:

sudo tailscale set --relay-server-port=40000

Użycie portu 0 wybiera losowy, nieużywany port, a pusty ciąg znaków wyłącza serwer przekaźnikowy. Następnie należy nadać urządzeniom klienckim uprawnienia do korzystania z niego za pomocą możliwości tailscale.com/cap/relay w pliku polityki sieci tailnet:

{
  "grants": [
    {
      "src": ["tag:us-east-vpc"],
      "dst": ["tag:us-east-relays"],
      "app": {
        "tailscale.com/cap/relay": []
      }
    }
  ]
}

Zarówno urządzenie przekaźnikowe, jak i urządzenia klienckie wymagają wersji Tailscale 1.86 lub nowszej, dlatego przed przystąpieniem do edycji pliku polityki należy sprawdzić wersję za pomocą tailscale version na każdym z nich. Warto zapamiętać kolejność prób nawiązywania połączenia przez demona. Najpierw podejmowana jest próba połączenia bezpośredniego. Jeśli zakończy się niepowodzeniem, demon szuka dostępnego peer relay. Jeśli żaden nie jest dostępny, następuje powrót do DERP. DERP nigdy nie jest całkowicie wykluczony, ponieważ stanowi również kanał, przez który maszyny negocjują połączenie.

Przyczyna 1: firewall wyjściowy blokujący UDP

Dokumentacja Tailscale wskazuje dwa powody, dla których połączenie pozostaje przekaźnikowe (relayed), a pierwszym z nich jest zablokowany ruch UDP. Należy zapytać o to bezpośrednio maszynę:

tailscale netcheck

Raport jest w tym miejscu skrócony, a najważniejsze pole, które o wszystkim decyduje, znajduje się na samej górze:

Report:
  * UDP: true
  * IPv4: yes, 203.0.113.9:41641
  * IPv6: no
  * MappingVariesByDestIP: false
  * PortMapping:
  * Nearest DERP: Dallas

UDP: false to najsilniejsza wskazówka, jaką można otrzymać. Oznacza ona, że maszyna nie była w stanie wysłać pakietu UDP do serwerów testowych Tailscale. Dokumentacja CLI Tailscale podaje, że w takim przypadku „jest mało prawdopodobne, aby Tailscale było w stanie nawiązać połączenia bezpośrednie (point-to-point)”. Ruch jest wówczas przesyłany przez DERP za pomocą portu TCP 443. Ten mechanizm awaryjny sprawia, że maszyna wygląda na w pełni sprawną: jest połączona, osiągalna, a każdy bajt danych jest przekazywany dalej. netcheck opisuje stan sieci, a nie konkretne połączenie, dlatego przed zmianą reguł należy potwierdzić ścieżkę używaną przez peer za pomocą tailscale status lub tailscale ping.

Zalecane są dwie reguły wyjściowe. „Pozwól urządzeniom wewnętrznym na inicjowanie ruchu UDP z :41641 do *:*”, co stanowi sam ruch WireGuard, oraz „Pozwól urządzeniom wewnętrznym na inicjowanie ruchu UDP do *:3478”, co dotyczy STUN (session traversal utilities for NAT) – protokołu używanego przez maszynę do poznania własnego publicznego adresu i portu. Dla miejsc docelowych należy stosować symbole wieloznaczne (wildcards). Tailscale z czasem dodaje nowe serwery przekaźnikowe, więc ręcznie utworzona lista adresów stanie się nieaktualna w ciągu roku.

Na wynajętym serwerze typową przyczyną jest restrykcyjna polityka wyjściowa (egress policy), wynikająca z użycia zabezpieczonego obrazu systemu lub narzucona przez dostawcę na poziomie infrastruktury. W pierwszej kolejności należy sprawdzić domyślną politykę wychodzącą:

sudo ufw status verbose
sudo nft list ruleset

Default: deny (incoming), allow (outgoing) jest poprawne i nie stanowi problemu. Domyślna polityka wychodząca ustawiona na deny z krótką listą dozwolonych portów (TCP 443 i DNS) jest dokładnie tym, co powoduje, że serwer pozostaje na stałe w trybie przekaźnikowym. Ścieżka DERP przez TCP 443 mieści się w tym ograniczeniu, natomiast ścieżka bezpośrednia nie. Miejsce, w którym te reguły faktycznie się znajdują, zależy od tego, czy system używa iptables czy nftables, a edycja niewłaściwego narzędzia jest częstym powodem braku efektów.

Strona przychodząca również ma znaczenie, ponieważ VPS posiada publiczny adres IP i może stanowić łatwiejszą stronę połączenia. Jeśli firewall akceptuje przychodzący ruch UDP na porcie, na którym nasłuchuje tailscaled, peery znajdujące się za trudnymi routerami domowymi mogą nawiązać z nim połączenie bez dodatkowych sztuczek. Należy sprawdzić port, który jest aktualnie używany:

sudo ss -lunp | grep tailscaled
sudo ufw allow 41641/udp

41641 to domyślny port statyczny. Jeśli w tailnet włączono ustawienie randomizeClientPort, klienci wybierają losowy port. W takim przypadku należy pobrać rzeczywisty numer z wyniku ss, zamiast polegać na wartości z tej strony. Następnie należy sprawdzić panel sterowania dostawcy. Większość dostawców stosuje firewall sieciowy niezależny od tego wewnątrz serwera, więc reguła dodana za pomocą ufw nie będzie miała na niego wpływu.

Przyczyna 2: restrykcyjny NAT po jednej lub obu stronach

Drugą udokumentowaną przyczyną jest restrykcyjny NAT (hard NAT). NAT (network address translation) to mechanizm routera polegający na zamianie adresu prywatnego na publiczny. Przyjazny router zachowuje ten sam port publiczny dla danego gniazda wewnętrznego niezależnie od odbiorcy, co nazywa się endpoint independent mapping. Restrykcyjny NAT przydziela inny port publiczny dla każdego miejsca docelowego, więc adres uzyskany przez maszynę z serwera STUN nie jest adresem, którego będzie mógł użyć partner połączenia. Tailscale raportuje to w netcheck jako MappingVariesByDestIP: true.

Jeden restrykcyjny NAT jest możliwy do obejścia. Jeśli druga strona posiada stabilny publiczny punkt końcowy, maszyna za restrykcyjnym NAT-em może nawiązać połączenie i ścieżka zostanie utworzona. Dwa restrykcyjne NAT-y jednocześnie powodują błąd, ponieważ żadna ze stron nie jest w stanie przewidzieć portu, na którym pojawi się druga.

Na serwerze VPS z publicznym adresem IPv4 to pole powinno zazwyczaj wskazywać false. Publiczny adres nie zawsze oznacza brak translacji: AWS na przykład dokumentuje, że jego internet gateway wykonuje translację jeden-do-jednego (one-to-one NAT) między publicznym adresem IPv4 instancji a jej adresem prywatnym. Statyczne mapowanie jeden-do-jednego zachowuje ten sam port niezależnie od odbiorcy, więc nie jest to restrykcyjny NAT i pole nadal wskazuje false. Jeśli na wynajmowanym serwerze widnieje true, oznacza to, że element sieci dostawcy przydziela inny port dla każdego miejsca docelowego i żadna reguła firewalla wewnątrz maszyny tego nie zmieni. Dostępne opcje to uruchomienie przekaźnika (peer relay) na maszynie z poprawnym publicznym punktem końcowym lub przeniesienie obciążenia. Jest to również przypadek, w którym ogłaszanie prywatnych zakresów adresowych z poziomu subnet routera przynosi korzyści, ponieważ wymagane jest wtedy jedno poprawne połączenie do sieci, zamiast poprawnej ścieżki do każdego urządzenia z osobna.

Dlaczego węzeł wyjściowy sprawia, że Tailscale wydaje się wolniejszy niż w rzeczywistości

Węzeł wyjściowy (exit node) stanowi drugi przeskok, a użytkownicy często niesłusznie obwiniają tunel za spowolnienie. Przy aktywnym węźle wyjściowym żądanie opuszcza laptopa, przechodzi przez tunel do VPS, opuszcza VPS, trafiając do publicznego Internetu, a odpowiedź wraca tą samą drogą. Nawet idealnie bezpośrednie połączenie z tym VPS nie może być szybsze niż jego własne łącze, a dodatkowy dystans jest odczuwalny przy ładowaniu każdej strony.

Należy zmierzyć obie części osobno. Wyłącz węzeł wyjściowy, a następnie przetestuj sam tunel, łącząc się z adresem tailnet serwera VPS:

sudo tailscale set --exit-node=
sudo apt install -y iperf3
iperf3 -s

Uruchom iperf3 -c 100.113.160.82 z poziomu klienta, kierując ruch na ten adres tailnet. Uzyskana wartość to przepustowość tunelu. Teraz włącz ponownie węzeł wyjściowy za pomocą sudo tailscale set --exit-node=100.113.160.82 i przeprowadź standardowy test prędkości do publicznego Internetu. Uzyskana wartość to przepustowość tunelu powiększona o ograniczenia łącza VPS. Jeśli pierwsza wartość jest zadowalająca, a druga niska, problem nie leży w Tailscale, lecz w sieci i wydajności samego węzła wyjściowego. Polecenie tailscale exit-node list wyświetla dostępne opcje, jeśli nie masz pewności, który węzeł został wybrany.

Drugim czynnikiem ograniczającym wydajność węzła wyjściowego jest procesor. Wytyczne Tailscale wskazują, że lepiej wybrać nowszą generację procesora o wyższym taktowaniu niż większą liczbę rdzeni, dlatego plan z większą liczbą vCPU nie zawsze oznacza wyższą prędkość. Na obciążonym współdzielonym hoście deklarowana moc obliczeniowa nie zawsze jest w pełni dostępna, a czas kradziony przez sąsiednie procesy (steal time) objawia się zmienną przepustowością, która waha się w zależności od pory dnia, mimo braku zmian w konfiguracji po Twojej stronie.

Jedyny parametr optymalizacji: rx-udp-gro-forwarding

Tailscale dokumentuje jedno ustawienie systemu Linux, które dotyczy maszyn przekazujących ruch, czyli węzłów wyjściowych (exit nodes) oraz routerów podsieci. Zwykły klient nie odnosi korzyści z tej zmiany. Wymagana jest wersja Tailscale 1.54 lub nowsza oraz jądro Linux w wersji 6.2 lub nowszej, dlatego należy zweryfikować oba wymagania przed wprowadzeniem zmian:

tailscale version
uname -r

Po spełnieniu tych warunków należy włączyć przekazywanie UDP GRO (generic receive offload) na interfejsie skierowanym do Internetu:

NETDEV=$(ip -o route get 8.8.8.8 | cut -f 5 -d " ")
sudo ethtool -K $NETDEV rx-udp-gro-forwarding on rx-gro-list off

Należy sprawdzić, czy zmiana została zastosowana:

ethtool -k $NETDEV | grep -E 'rx-udp-gro-forwarding|rx-gro-list'

Powinny pojawić się wartości rx-udp-gro-forwarding: on oraz rx-gro-list: off. Powodem, dla którego to rozwiązanie pomaga, jest fakt, że ruch Tailscale opiera się na protokole UDP. Pozwolenie jądru na utrzymywanie scalonych małych pakietów UDP w trakcie przekazywania oznacza, że demon obsługuje mniej, ale większych segmentów dla tej samej liczby bajtów. Ustawienie ethtool -K nie przetrwa restartu, dlatego należy zapewnić jego trwałość. W systemie korzystającym z networkd-dispatcher:

printf '#!/bin/sh\n\nethtool -K %s rx-udp-gro-forwarding on rx-gro-list off \n' "$(ip -o route get 8.8.8.8 | cut -f 5 -d " ")" | sudo tee /etc/networkd-dispatcher/routable.d/50-tailscale
sudo chmod 755 /etc/networkd-dispatcher/routable.d/50-tailscale

Należy uruchomić skrypt ręcznie i sprawdzić, czy jego kod wyjścia wynosi 0. Węzeł przekazujący wymaga również włączonego przekazywania IP, co stanowi osobne ustawienie i potencjalne źródło awarii:

echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

Jaką wartość MTU wykorzystuje interfejs tailscale0?

Zamiast zgadywać, należy odczytać tę wartość bezpośrednio z systemu:

ip link show tailscale0

Wartość mtu w powyższym wyniku to rzeczywisty parametr używany przez tunel. Jest on niższy niż 1500, czyli wartość raportowana przez interfejs ethernet. Jest to działanie zamierzone, a nie błąd. Każdy pakiet wysyłany wewnątrz tunelu jest enkapsulowany: zewnętrzny nagłówek IP zajmuje 20 bajtów dla IPv4 lub 40 dla IPv6, nagłówek UDP 8 bajtów, a ramka WireGuard wraz z tagiem uwierzytelniającym kolejne 32 bajty. Całość musi zmieścić się w limicie narzuconym przez fizyczną ścieżkę transmisji. Tailscale dobiera wartość wystarczająco niską, aby zapewnić poprawne działanie na łączach obsługujących mniej niż pełne 1500 bajtów, co dotyczy połączeń PPPoE, niektórych sieci mobilnych oraz tuneli IPv6.

Objawy problemów z MTU są specyficzne, dlatego nie należy diagnozować ich wyłącznie na podstawie spadku wydajności. Sesja SSH pozostaje responsywna, polecenie ping działa poprawnie, natomiast duże transfery danych lub rozbudowane strony HTTPS całkowicie zawieszają się zamiast po prostu działać wolniej. Taki wzorzec oznacza, że zbyt duże pakiety są odrzucane w pewnym punkcie trasy, a do nadawcy nie dociera żaden komunikat ICMP informujący o tym fakcie. Zwiększanie wartości MTU tailscale0 w kierunku 1500 pogarsza sytuację, ponieważ pakiety, które już teraz nie mieszczą się w limicie, stają się jeszcze większe. Prawidłowym rozwiązaniem jest wyznaczenie poprawnej wartości MTU ścieżki metodą bisekcji i ograniczenie TCP MSS na routerze przekazującym ruch. Typ połączenia nie zmienia tej zasady: ścieżka przekaźnikowa (relayed) oraz bezpośrednia korzystają z tej samej wartości MTU interfejsu.

FAQ

Skąd mam wiedzieć, czy moje połączenie Tailscale jest bezpośrednie czy przekaźnikowe?

Uruchom tailscale status i odczytaj końcówkę linii danego węzła. direct 203.0.113.9:41641 oznacza połączenie bezpośrednie, relay "tor" oznacza, że każdy pakiet przechodzi przez serwer DERP, a peer-relay oznacza, że pakiety przechodzą przez maszynę w Twojej własnej sieci tailnet. Aby uzyskać drugą opinię, uruchom tailscale ping <peer>: poprawna ścieżka zaczyna się od DERP, a następnie wyświetla pong z adresem i portem, podczas gdy ścieżka przekaźnikowa wyświetla pongi DERP, aż do zakończenia działania z direct connection not established. Wyślij najpierw trochę ruchu do węzła, ponieważ Tailscale buduje ścieżkę tylko na żądanie.

Dlaczego mój VPS nigdy nie uzyskuje połączenia bezpośredniego?

Uruchom tailscale netcheck na VPS. Jeśli wyświetli UDP: false, ruch wychodzący UDP jest blokowany, najczęściej przez zaporę sieciową typu egress, a Tailscale twierdzi, że połączenie punkt-punkt jest w takim przypadku mało prawdopodobne. Potwierdź za pomocą tailscale status lub tailscale ping, czy węzeł korzysta z DERP, który działa przez TCP 443, co wyjaśnia, dlaczego maszyna nadal wydaje się połączona. Zezwól na ruch wychodzący UDP z portu 41641 do dowolnego miejsca docelowego oraz na ruch wychodzący UDP do dowolnego miejsca docelowego na porcie 3478. Sprawdź zaporę sieciową dostawcy oraz zaporę wewnątrz serwera, ponieważ są to oddzielne mechanizmy kontroli, a reguła ufw nie wpływa na ustawienia dostawcy.

Czy przekaźnikowe połączenie Tailscale jest mniej bezpieczne niż bezpośrednie?

Nie. Serwer DERP przesyła pakiety WireGuard, których nie może odszyfrować, ponieważ klucze szyfrujące są generowane na urządzeniach i nigdy ich nie opuszczają. Kosztem przekaźnika są opóźnienia i przepustowość, a nie poufność. Serwer koordynujący kontroluje jedynie to, które urządzenia dowiadują się o sobie nawzajem, a podział między materiałem kluczy a metadanymi połączenia warto zrozumieć przed podjęciem decyzji o samodzielnym hostowaniu infrastruktury.

Czy ustawienie rx-udp-gro-forwarding pomaga każdej maszynie?

Nie. Jest ono udokumentowane dla maszyn z systemem Linux, które przekazują ruch dla innych, czyli węzłów wyjściowych (exit nodes) i routerów podsieci. Laptop lub serwer, który komunikuje się tylko z własnymi węzłami, nie zyskuje na tym nic. Wymaga to również Tailscale w wersji 1.54 lub nowszej oraz jądra Linux 6.2 lub nowszego, więc sprawdź najpierw tailscale version i uname -r, pamiętając, że ethtool -K resetuje się po restarcie, chyba że zostanie utrwalone.

Czy Tailscale jest wolniejsze od zwykłego WireGuard?

Oba rozwiązania używają WireGuard do szyfrowania ruchu. Tailscale dodaje konfigurację połączenia, którą w zwykłym WireGuard trzeba wykonać ręcznie, a ta konfiguracja jest powodem, dla którego czasami połączenie trafia na przekaźnik. Zwykły WireGuard nie ma przekaźników: łączy się bezpośrednio albo w ogóle nie nawiązuje połączenia. Należy więc porównywać porównywalne rzeczy i testować wydajność Tailscale tylko wtedy, gdy tailscale status wskazuje direct. Jeśli chcesz porównać wersję skonfigurowaną ręcznie, serwer WireGuard zbudowany samodzielnie wymaga około czterdziestu linii konfiguracji, a kompromisy z tym związane zostały opisane w porównaniu obu podejść.