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

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

TL;DR 版本

观看这个 ASCII 转换或者这个视频- 然后找出发生这种情况的任何原因。下面的文字描述提供了更多上下文。

设置详情

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

拓扑结构

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

当使用 进行基准测试时iperf3,笔记本电脑和 SBC 之间的链接低于理论上的 56 MBits/秒 - 正如预期的那样,因为这是非常“拥挤的 2.4GHz”内的 WiFi 连接(公寓楼)

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

# 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

所以基本上,上传到 SBC 的速度约为 24MBits/秒,从 SBC 下载 ( -R) 的速度达到 32MBits/秒。

使用 SSH 进行基准测试

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

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

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

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

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

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

不,绝对不是。

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

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

不,这工作得很好 - 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

在笔记本电脑中:

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

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

那么如果我使用 ProxyCommand 运行 SSH 来使用 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]

作品! 10 倍速度。

现在我有点困惑 - 当使用“naked”nc作为 时Proxycommand,你基本上不是在做与 SSH 完全相同的事情吗?即创建一个套接字,连接到 SBC 的端口 22,然后在其上添加 SSH 协议?

为什么最终的速度会有如此巨大的差异?

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

编辑:添加了过程的“视频”这里。计算从 ifconfig 的输出发送的数据包,很明显,在这两个测试中,我们发送了 40MB 的数据,以大约 30K 数据包的形式传输它们 - 只是在不使用时速度要慢得多ProxyCommand

答案1

非常感谢在评论中提出想法的人们。我经历了所有这些:

使用tcpdump记录数据包并在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

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

检查流量整形

对此一无所知 - 但在查看“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 

...这似乎没有区分“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

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

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>

这会设置 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 [email protected] 'cat >/dev/null'
No, Mr Nagle stays.
No, Mr Nagle stays.
 100MiB 0:00:29 [3.38MiB/s] [3.38MiB/s] [================================>] 100%   

那里 - 完美的速度(嗯,和 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
...

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

相关内容