通过 ProxyCommand 大大提高了 SSH 速度 - 但为什么呢?

tts*_*ras 17 networking ssh netcat

TL;DR 版本

观看此 ASCII 演员表此视频- 然后找出发生这种情况的任何原因。下面的文字说明提供了更多的上下文。

设置细节

  • 机器 1 是一台 Arch Linux 笔记本电脑,在其ssh上生成,连接到运行 Armbian 的 SBC(橙色 PI 零)。
  • SBC 本身通过以太网连接到 DSL 路由器,IP 为 192.168.1.150
  • 笔记本电脑通过 WiFi 连接到路由器 - 使用官方 Raspberry PI WiFi 加密狗。
  • 还有另一台笔记本电脑(机器 2)通过以太网连接到 DSL 路由器。

拓扑

使用 iperf3 对链接进行基准测试

以 为基准时iperf3,笔记本电脑和 SBC 之间的链接低于理论 56 Mbits/sec - 正如预期的那样,因为这是一个非常“拥挤的 2.4GHz” (公寓楼)内的 WiFi 连接。

更具体地说:在iperf3 -sSBC 上运行后,在笔记本电脑上执行以下命令:

# 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
Run Code Online (Sandbox Code Playgroud)

所以基本上,上传到 SBC 达到大约 24MBits/sec,从它 ( -R)下载达到 32MBits/sec。

使用 SSH 进行基准测试

鉴于此,让我们看看 SSH 的表现如何。我在使用rsyncand时第一次遇到导致这篇文章的问题borgbackup- 他们都使用 SSH 作为传输层......所以让我们看看 SSH 在同一链接上的表现:

# cat /dev/urandom | \
    pv -ptebar | \
    ssh  root@192.168.1.150 'cat >/dev/null'
20.3MiB 0:00:52 [ 315KiB/s] [ 394KiB/s]
Run Code Online (Sandbox Code Playgroud)

嗯,这速度太快了!比预期的链接速度慢得多...... (如果您不知道pv -ptevar:它显示通过它的数据的当前和平均速率。在这种情况下,我们看到从/dev/urandomSSH读取数据并将数据发送到 SBC平均达到 400KB/s - 即 3.2MBits/sec,远低于预期的 24MBits/sec。)

为什么我们的链路以 13% 的容量运行?

也许是我们/dev/urandom的错?

# cat /dev/urandom | pv -ptebar > /dev/null
834MiB 0:00:04 [ 216MiB/s] [ 208MiB/s]
Run Code Online (Sandbox Code Playgroud)

不,绝对不是。

可能是 SBC 本身吗?可能是处理太慢了?让我们尝试运行相同的 SSH 命令(即向 SBC 发送数据),但这次是从通过以太网连接的另一台机器(机器 2)运行:

# cat /dev/urandom | \
    pv -ptebar | \
    ssh  root@192.168.1.150 'cat >/dev/null'
240MiB 0:00:31 [10.7MiB/s] [7.69MiB/s] 
Run Code Online (Sandbox Code Playgroud)

不,这工作正常 - SBC 上的 SSH 守护程序可以(轻松)处理其以太网链接提供的 11MBytes/sec(即 100MBits/sec)。

执行此操作时是否加载了 SBC 的 CPU?

CPU很容易处理它

不。

所以...

  • 在网络方面(按照iperf3),我们应该能够将速度提高 10 倍
  • 我们的 CPU 可以轻松适应负载
  • ...我们不涉及任何其他类型的 I/O(例如驱动器)。

到底发生了什么?

Netcat 和 ProxyCommand 来救援

让我们尝试普通的旧netcat连接 - 它们的运行速度是否如我们预期的一样快?

在 SBC 中:

# nc -l -p 9988 | pv -ptebar > /dev/null
Run Code Online (Sandbox Code Playgroud)

在笔记本电脑中:

# cat /dev/urandom | pv -ptebar | nc 192.168.1.150 9988
117MiB 0:00:33 [3.82MiB/s] [3.57MiB/s] 
Run Code Online (Sandbox Code Playgroud)

有用!并以预期的速度运行 - 好得多,好 10 倍 - 速度。

那么如果我使用 ProxyCommand 运行 SSH 来使用 nc 会发生什么?

# cat /dev/urandom | \
    pv -ptebar | \
    ssh -o "Proxycommand nc %h %p" root@192.168.1.150 'cat >/dev/null'
101MiB 0:00:30 [3.38MiB/s] [3.33MiB/s]
Run Code Online (Sandbox Code Playgroud)

作品!10 倍速度。

现在我有点困惑——当使用“裸”nc作为 时Proxycommand,你是不是在做与 SSH 完全相同的事情?即创建一个套接字,连接到 SBC 的端口 22,然后通过它铲起 SSH 协议?

为什么产生的速度会有如此巨大的差异?

PS 这不是学术练习- 因此我的borg备份运行速度快了 10 倍。我只是不知道为什么:-)

编辑:在此处添加了该过程的“视频” 。计算从 ifconfig 输出发送的数据包,很明显,在两个测试中,我们发送了 40MB 的数据,以大约 30K 的数据包传输它们 - 不使用ProxyCommand.

tts*_*ras 15

非常感谢在评论中提交想法的人。我都经历了:

使用tcpdump记录数据包并比较WireShark中的内容

# tcpdump -i wlan0 -w good.ssh & \
     cat signature | ssh -o "ProxyCommand nc %h %p" \
        root@192.168.1.150 'cat | md5sum' ; \
     killall tcpdump
# tcpdump -i wlan0 -w bad.ssh & \
     cat signature | ssh root@192.168.1.150 'cat | md5sum' ; \
     killall tcpdump
Run Code Online (Sandbox Code Playgroud)

在记录的数据包中没有任何重要的差异。

检查流量整形

对此一无所知 - 但在查看“tc”联机帮助页后,我能够验证

  • tc filter show 什么都不返回
  • tc class show 什么都不返回
  • tc qdisc show

...返回这些:

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 
Run Code Online (Sandbox Code Playgroud)

...这似乎没有区分“ssh”和“nc” - 事实上,我什至不确定流量整形是否可以在进程级别上运行(我希望它可以在地址/端口/区分IP 标头中的服务字段)。

Debian Chroot,避免 Arch Linux SSH 客户端潜在的“聪明”

不,同样的结果。

最后——纳格尔

在发件人中执行 strace...

pv data | strace -T -ttt -f ssh 192.168.1.150 'cat | md5sum' 2>bad.log
Run Code Online (Sandbox Code Playgroud)

...并查看传输数据的套接字上究竟发生了什么,我在实际传输开始之前注意到了这个“设置”:

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>
Run Code Online (Sandbox Code Playgroud)

这将设置 SSH 套接字以禁用 Nagle 算法。您可以谷歌并阅读所有相关信息 - 但它的意思是,SSH 优先考虑响应能力而不是带宽 - 它指示内核立即传输写入此套接字的任何内容,而不是“延迟”等待来自远程的确认。

简而言之,这意味着在其默认配置中,SSH 不是传输数据的好方法 - 当使用的链接速度较慢时(许多 WiFi 链接都是这种情况)。如果我们通过空中发送“主要是报头”的数据包,带宽就被浪费了!

为了证明这确实是罪魁祸首,我使用 LD_PRELOAD 来“删除”这个特定的系统调用:

$ 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 root@192.168.1.150 'cat >/dev/null'
No, Mr Nagle stays.
No, Mr Nagle stays.
 100MiB 0:00:29 [3.38MiB/s] [3.38MiB/s] [================================>] 100%   
Run Code Online (Sandbox Code Playgroud)

那里 - 完美的速度(嗯,就像 iperf3 一样快)。

故事的士气

永不放弃 :-)

如果您确实使用类似rsyncborgbackup通过 SSH 传输数据的工具,并且您的链接速度很慢,请尝试阻止 SSH 禁用 Nagle(如上所示)-或使用ProxyCommand将 SSH 切换为通过nc. 这可以在您的 $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
...
Run Code Online (Sandbox Code Playgroud)

...以便将来在 ssh/rsync/borgbackup 中将“orangepi”用作目标主机的所有用途都将nc用于连接(因此不要理会Nagle)。