Szybkość SSH znacznie poprawiona przez ProxyCommand - ale dlaczego?

14

Wersja TL; DR

Obejrzyj tę obsadę ASCII lub wideo - a następnie wymyśl wszelkie powody, dla których tak się dzieje. Poniższy opis tekstowy zapewnia większy kontekst.

Szczegóły konfiguracji

  • Maszyna 1 to laptop Arch Linux, na którym sshodradza się, łączący się z SBC działającym pod kontrolą Armbiana (Orange PI Zero).
  • Sam SBC jest podłączony przez Ethernet do routera DSL i ma adres IP 192.168.1.150
  • Laptop jest podłączony do routera przez WiFi - za pomocą oficjalnego klucza Raspberry PI WiFi.
  • Istnieje również inny laptop (Maszyna 2) podłączony przez Ethernet do routera DSL.

Topologia

Benchmarking linku za pomocą iperf3

W testach porównawczych iperf3połączenie między laptopem a SBC jest mniejsze niż teoretyczne 56 MB / s - zgodnie z oczekiwaniami, ponieważ jest to połączenie Wi-Fi w bardzo „zatłoczonym 2,4 GHz” (budynek mieszkalny) .

Mówiąc dokładniej: po uruchomieniu iperf3 -sna SBC na laptopie wykonywane są następujące polecenia:

# iperf3 -c 192.168.1.150
Connecting to host 192.168.1.150, port 5201
[  5] local 192.168.1.89 port 57954 connected to 192.168.1.150 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  2.99 MBytes  25.1 Mbits/sec    0    112 KBytes       
...
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  28.0 MBytes  23.5 Mbits/sec    5             sender
[  5]   0.00-10.00  sec  27.8 MBytes  23.4 Mbits/sec                  receiver

iperf Done.

# iperf3 -c 192.168.1.150 -R
Connecting to host 192.168.1.150, port 5201
Reverse mode, remote host 192.168.1.150 is sending
[  5] local 192.168.1.89 port 57960 connected to 192.168.1.150 port 5201
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-1.00   sec  3.43 MBytes  28.7 Mbits/sec                  
...                
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  39.2 MBytes  32.9 Mbits/sec  375             sender
[  5]   0.00-10.00  sec  37.7 MBytes  31.6 Mbits/sec                  receiver

W zasadzie przesyłanie do SBC osiąga około 24 MB / s, a pobieranie z niego ( -R) osiąga 32 MB / s.

Benchmarking z SSH

Biorąc to pod uwagę, zobaczmy, jak radzi sobie SSH. Najpierw doświadczyłem problemów, które doprowadziły do ​​tego postu podczas używania rsynci borgbackup- oba używają SSH jako warstwy transportowej ... Zobaczmy więc, jak SSH działa na tym samym łączu:

# cat /dev/urandom | \
    pv -ptebar | \
    ssh  [email protected] 'cat >/dev/null'
20.3MiB 0:00:52 [ 315KiB/s] [ 394KiB/s]

To niesamowita prędkość! Znacznie wolniejsza niż oczekiwana prędkość łącza ... (W przypadku, gdy nie jesteś świadomy pv -ptevar: wyświetla bieżącą i średnią szybkość danych przez nią przechodzących. W tym przypadku widzimy, że odczytuje /dev/urandomi wysyła dane przez SSH do SBC osiąga średnio 400 KB / s - tj. 3,2 MB / s, czyli znacznie mniej niż oczekiwane 24 MB / s.)

Dlaczego nasze łącze działa przy 13% swojej pojemności?

Czy to może nasza /dev/urandomwina?

# cat /dev/urandom | pv -ptebar > /dev/null
834MiB 0:00:04 [ 216MiB/s] [ 208MiB/s]

Nie, zdecydowanie nie.

Czy to może sama SBC? Być może jest zbyt wolny do przetworzenia? Spróbujmy uruchomić to samo polecenie SSH (tj. Wysłać dane do SBC), ale tym razem z innej maszyny (Maszyna 2), która jest podłączona przez Ethernet:

# cat /dev/urandom | \
    pv -ptebar | \
    ssh  [email protected] 'cat >/dev/null'
240MiB 0:00:31 [10.7MiB/s] [7.69MiB/s] 

Nie, to działa dobrze - demon SSH na SBC może (łatwo) obsłużyć 11 MB / s (tj. 100 MB / s), które zapewnia łącze Ethernet.

I czy podczas tego procesu ładowany jest procesor SBC?

Procesor łatwo sobie z tym radzi

Nie.

Więc...

  • pod względem sieci (zgodnie z iperf3) powinniśmy być w stanie osiągnąć 10-krotną prędkość
  • nasz procesor może łatwo wytrzymać obciążenie
  • ... i nie angażujemy żadnego innego rodzaju We / Wy (np. dysków).

Co się do cholery dzieje?

Netcat i ProxyCommand na ratunek

Wypróbujmy stare, proste netcatpołączenia - czy działają tak szybko, jak byśmy się spodziewali?

W SBC:

# nc -l -p 9988 | pv -ptebar > /dev/null

W laptopie:

# cat /dev/urandom | pv -ptebar | nc 192.168.1.150 9988
117MiB 0:00:33 [3.82MiB/s] [3.57MiB/s] 

To działa! I działa z oczekiwaną - znacznie lepszą, 10-krotnie lepszą - prędkością.

Co się stanie, jeśli uruchomię SSH za pomocą ProxyCommand, aby użyć nc?

# cat /dev/urandom | \
    pv -ptebar | \
    ssh -o "Proxycommand nc %h %p" [email protected] 'cat >/dev/null'
101MiB 0:00:30 [3.38MiB/s] [3.33MiB/s]

Pracuje! 10-krotna prędkość.

Teraz jestem trochę zdezorientowany - kiedy używasz „nagiego” ncjako Proxycommand, czy zasadniczo nie robisz dokładnie tego samego, co robi SSH? tj. utworzenie gniazda, połączenie z portem SBC 22, a następnie przerzucenie protokołu SSH nad nim?

Dlaczego jest tak ogromna różnica w wynikowej prędkości?

PS To nie było ćwiczenie akademickie - borgz tego powodu moja kopia zapasowa działa 10 razy szybciej. Po prostu nie wiem dlaczego :-)

EDIT : Dodano „Video” procesu tutaj . Licząc pakiety wysłane z wyjścia ifconfig, jasne jest, że w obu testach wysyłamy 40 MB danych, przesyłając je w pakietach około 30 KB - po prostu znacznie wolniej, gdy nie są używane ProxyCommand.

ttsiodras
źródło
buforowanie? Myślę, że ncużywa buforowania linii, podczas gdy sshnie ma buforowania. Tak więc (lub jeśli tak) ruch ssh obejmuje więcej pakietów.
Ralph Rönnquist
nie jestem ekspertem, ale myślę, że pomarańczowy 0 ma tylko jedną magistralę USB kontrolowaną przez procesor, sieć przechodzi przez tę magistralę USB, procesor musi utworzyć losową liczbę za pomocą oprogramowania (nie ma chipa w tego rodzaju architekturze, który robi to za pośrednictwem sprzęt), a jednocześnie trwa szyfrowanie ssh i być może także kompresja ssh. nie sprawdziłem tego wszystkiego, więc możliwe, że mówię coś nie tak.
D'Arcy Nader
2
@ D'ArcyNader: Nie, obawiam się, że pomyliłeś się. Tbe / dev / urandom dzieje się na laptopie (x86) - i zrobiłem ten sam test z Machine 2 mówiąc do SBC, osiągając najwyższe prędkości (100 MBits / s), a tym samym udowadniając, że SBC nie ma problemu z radzeniem sobie z ruchem. Problem pojawia się tylko wtedy, gdy SSH jest używany z laptopa - i kiedy zmieniam wywołanie SSH (ponownie, po stronie laptopa), aby używać netcat - więc nadal robię dev / urandom i nadal przesyłam wszystkie dane - problem znika. A tak przy okazji, pojedyncza magistrala USB jest problemem Raspberry PI - nie Orange PI.
ttsiodras
przepraszam, jeśli ci nie pomogłem. i dziękuję za wyjaśnienie.
D'Arcy Nader
@ RalphRönnquist: Pierwotnym przypadkiem użycia, który poprowadził mnie do tej króliczej nory, było tworzenie kopii zapasowych rsync i borgbackup. Wiele narzędzi wykorzystuje SSH jako mechanizm transportu - i w moim przypadku ucierpiał z tego powodu. Jeśli rzeczywiście mam do czynienia ze „standardowym” zachowaniem SSH, to oczekiwałbym, że przesyłanie żądań ściągania do wszystkich narzędzi do tworzenia kopii zapasowych, aby spawnować SSH za pomocą netcat ProxyCommand, natychmiast przyspieszy tworzenie kopii zapasowych na całej planecie! Nie mogę uwierzyć, że dokonałem tak „ogromnego” odkrycia :-) coś innego musi się tu wydarzyć.
ttsiodras

Odpowiedzi:

14

Ogromne podziękowania dla osób, które zgłosiły pomysły w komentarzach. Przejrzałem je wszystkie:

Nagrywanie pakietów za pomocą tcpdump i porównywanie zawartości w WireShark

# tcpdump -i wlan0 -w good.ssh & \
     cat signature | ssh -o "ProxyCommand nc %h %p" \
        [email protected] 'cat | md5sum' ; \
     killall tcpdump
# tcpdump -i wlan0 -w bad.ssh & \
     cat signature | ssh [email protected] 'cat | md5sum' ; \
     killall tcpdump

W zarejestrowanych pakietach nie było żadnej różnicy.

Sprawdzanie kształtowania ruchu

Nie miałem o tym pojęcia - ale po przejrzeniu strony „tc” udało mi się to zweryfikować

  • tc filter show nic nie zwraca
  • tc class show nic nie zwraca
  • tc qdisc show

... zwraca te:

qdisc noqueue 0: dev lo root refcnt 2
qdisc noqueue 0: dev docker0 root refcnt 2
qdisc fq_codel 0: dev wlan0 root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn 

... które wydają się nie rozróżniać między "ssh" i "nc" - w rzeczywistości nie jestem nawet pewien, czy kształtowanie ruchu może działać na poziomie procesu (oczekiwałbym, że będzie działać na adresach / portach / Zróżnicowany Pole Usługi w nagłówku IP).

Debian Chroot, aby uniknąć potencjalnej „sprytności” w kliencie SSH Arch Linux

Nie, te same wyniki.

Wreszcie - Nagle

Przeprowadzanie śledzenia nadawcy ...

pv data | strace -T -ttt -f ssh 192.168.1.150 'cat | md5sum' 2>bad.log

... i patrząc na to, co dokładnie dzieje się w gnieździe, przez które przesyłane są dane, zauważyłem tę „konfigurację” przed rozpoczęciem faktycznego przesyłania:

1522665534.007805 getsockopt(3, SOL_TCP, TCP_NODELAY, [0], [4]) = 0 <0.000025>
1522665534.007899 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000021>

To ustawia gniazdo SSH w celu wyłączenia algorytmu Nagle. Możesz Google i przeczytać o tym wszystko - ale to znaczy, że SSH daje pierwszeństwo responsywności nad przepustowością - instruuje jądro, aby natychmiast przesyłało wszystko, co napisano na tym gnieździe, a nie „opóźniało” oczekiwanie na potwierdzenia z pilota.

Mówiąc wprost, oznacza to, że w domyślnej konfiguracji, SSH NIE jest dobrym sposobem na przesyłanie danych - nie wtedy, gdy używane łącze jest wolne (co ma miejsce w przypadku wielu łączy WiFi). Jeśli wysyłamy pakiety bezprzewodowo, które są „głównie nagłówkami”, przepustowość jest marnowana!

Aby udowodnić, że to rzeczywiście był winowajca, użyłem LD_PRELOAD, aby „upuścić” ten konkretny system:

$ cat force_nagle.c

#include <stdio.h>
#include <dlfcn.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>

int (*osetsockopt) (int socket, int level, int option_name,
           const void *option_value, socklen_t option_len) = NULL;

int setsockopt(int socket, int level, int option_name,
           const void *option_value, socklen_t option_len)
{
    int ret;
    if (!osetsockopt) {
        osetsockopt = dlsym(RTLD_NEXT, "setsockopt");
    }

    if (option_name == TCP_NODELAY) {
        puts("No, Mr Nagle stays.");
        return 0;
    }
    ret = osetsockopt(socket, level, option_name, option_value, option_len);
    return ret;
}

$ gcc -fPIC -D_GNU_SOURCE -shared -o force_nagle.so force_nagle.c -ldl

$ pv /dev/shm/data | LD_PRELOAD=./force_nagle.so ssh [email protected] 'cat >/dev/null'
No, Mr Nagle stays.
No, Mr Nagle stays.
 100MiB 0:00:29 [3.38MiB/s] [3.38MiB/s] [================================>] 100%   

Tam - idealna prędkość (cóż, tak szybko jak iperf3).

Morale tej historii

Nigdy się nie poddawaj :-)

A jeśli korzystasz z takich narzędzi rsynclub takich, borgbackupktóre przenoszą ich dane przez SSH, a twój link jest wolny, spróbuj powstrzymać SSH przed wyłączeniem Nagle (jak pokazano powyżej) - lub użyj ProxyCommandprzełączania SSH do łączenia się za pośrednictwem nc. Można to zautomatyzować w $ HOME / .ssh / config:

$ cat .ssh/config
...
Host orangepi
    Hostname 192.168.1.150
    User root
    Port 22
    # Compression no
    # Cipher None
    ProxyCommand nc %h %p
...

... dzięki czemu wszystkie przyszłe zastosowania „orangepi” jako hosta docelowego w ssh / rsync / borgbackup będą odtąd używane ncdo łączenia się (a zatem zostawiają Nagle w spokoju).

ttsiodras
źródło
Dzięki, uratowałeś mi życie! Czy próbowałeś skontaktować się z ludźmi ssh, aby zrozumieć, dlaczego nie ma ustawień, aby to kontrolować?
static_rtti
1
Cieszę się, że moje odkrycia pomogły ci również! Jeśli chodzi o kontakt z ludźmi SSH, próbowałem, tak - ale nic się nie stało, w końcu: bugzilla.mindrot.org/show_bug.cgi?id=2848
ttsiodras
Dodałem się do błędu. Kto wie, w końcu coś może się wydarzyć! W każdym razie świetne śledztwo.
static_rtti