跳转到内容

OpenSSH/Cookbook/多路复用

100% developed
来自维基教科书,开放的书籍,开放的世界

 

多路复用是指通过单条线路或连接发送多个信号的能力。在 OpenSSH 中,多路复用可以重复使用现有的传出 TCP 连接,用于与远程 SSH 服务器的多个并发 SSH 会话,避免每次创建新的 TCP 连接和重新认证带来的开销。

多路复用的优势

[编辑 | 编辑源代码]

SSH 多路复用的一个优点是消除了创建新的 TCP 连接和协商安全连接的开销。一台机器可以接受的连接总数是一个有限的资源,并且在一些机器上比在其他机器上更明显,并且根据负载和使用情况而有很大差异。打开新连接时也会有很大的延迟。使用多路复用可以显着加快重复打开新连接的活动。

通过比较下面的表格,可以看出多路复用和独立会话之间的区别。两者都是从 netstat -nt 中选择输出,并进行了一些编辑以提高清晰度。我们看到在表 1,“SSH 连接,独立”中,如果没有多路复用,每次新登录都会创建一个新的 TCP 连接,每个登录会话一个。之后,我们在另一个表,表 2,“SSH 连接,多路复用”中看到,当使用多路复用时,新的登录会通过已经建立的 TCP 连接进行通道传输。

#              Local Address       Foreign Address         State

# one connection
tcp    0   0   192.168.x.y:45050   192.168.x.z:22       ESTABLISHED

# two separate connections
tcp    0   0   192.168.x.y:45050   192.168.x.z:22       ESTABLISHED
tcp    0   0   192.168.x.y:45051   192.168.x.z:22       ESTABLISHED

# three separate connections
tcp    0   0   192.168.x.y:45050   192.168.x.z:22       ESTABLISHED
tcp    0   0   192.168.x.y:45051   192.168.x.z:22       ESTABLISHED
tcp    0   0   192.168.x.y:45052   192.168.x.z:22       ESTABLISHED

Table 1: SSH Connections, Separate

两个表都显示了与 SSH 会话关联的 TCP/IP 连接。上面的表显示了每个新的 SSH 连接都有一个新的 TCP/IP 连接。下面的表显示了单个 TCP/IP 连接,尽管有多个活动的 SSH 会话。

#              Local Address       Foreign Address         State

# one connection
tcp    0   0   192.168.x.y:58913   192.168.x.z:22       ESTABLISHED

# two multiplexed connections
tcp    0   0   192.168.x.y:58913   192.168.x.z:22       ESTABLISHED

# three multiplexed connections
tcp    0   0   192.168.x.y:58913   192.168.x.z:22       ESTABLISHED

Table 2: SSH Connections, Multiplexed

正如我们从多路复用中看到的那样,无论是否有通过它传输的多个 SSH 会话,都只设置并使用单个 TCP 连接。

或者我们可以比较使用 true(1) 在一个慢速远程服务器上运行 time(1) 所需的时间。这两个命令类似于 time ssh server.example.org truetime ssh -S ./path/to/somesocket server.example.org true,并为每个命令使用代理中的密钥。首先,在没有多路复用的情况下,我们看到正常的连接时间

real    0m0.658s
user    0m0.016s
sys     0m0.008s

然后我们再次做同样的事情,但是使用多路复用连接来查看更快的结果

real    0m0.029s
user    0m0.004s
sys     0m0.004s

差异相当大,并且对于任何活动来说都会累积起来,这些活动在这些活动中,连接会以快速连续的方式重复进行。多路复用连接的速度增益不会与主连接相关,主连接是正常速度,而是与第二个和后续的多路复用连接相关。新的 SSH 连接的开销仍然存在,但新的 TCP 连接的开销被避免了。第二个和之后的连接将重复使用已建立的 TCP 连接,并且不需要为每个新的 SSH 连接创建新的 TCP 连接。

设置多路复用

[编辑 | 编辑源代码]

OpenSSH 客户端支持对其传出连接进行多路复用,从版本 3.9(2004 年 8 月 18 日)[1] 开始,使用 ControlMasterControlPathControlPersist 配置指令,这些指令在 ssh_config(5) 中定义。客户端配置文件通常默认位于 ~/.ssh/config 位置。所有三个指令都在 ssh_config(5) 的手册页中进行了描述。还可以参考那里的“TOKENS”部分,了解可用于 ControlPath 的令牌列表。在运行时会扩展所有使用的有效令牌。

ControlMaster 决定 ssh(1) 是否会监听控制连接以及如何处理它们。ControlPath 设置多路复用会话使用的控制套接字的位置。这些可以在 ssh_config(5) 中全局或本地定义,或者在运行时指定。控制套接字在主连接结束时会自动删除。ControlPersist 可以与 ControlMaster 结合使用。如果将 ControlPersist 设置为“yes”,则它将在后台保持主连接打开,以接受新的连接,直到明确终止或使用 -O 关闭,或者在预定义的超时时间结束。如果将 ControlPersist 设置为一个时间,则它将在指定的时间内保持主连接打开,或者直到最后一个多路复用会话关闭,以较长的为准。

以下是从 ssh_config(5) 中摘录的示例,适用于通过快捷方式 machine1 启动到 machine1.example.org 的多路复用会话。

Host machine1
        HostName machine1.example.org
        ControlPath ~/.ssh/controlmasters/%r@%h:%p
        ControlMaster auto
        ControlPersist 10m

使用该配置,与 machine1 的第一个连接将在目录 ~/.ssh/controlmasters/ 中创建一个控制套接字。然后,任何后续连接(默认最多 10 个,由 SSH 服务器上的 MaxSessions 设置)将自动重新使用该控制路径作为多路复用会话。如果将 ControlMaster 设置为“autoask”而不是“auto”,则可以要求确认每个新连接。

请注意,在上面的设置中,控制套接字将被放置在文件夹 ~/.ssh/controlmasters/ 中。如果该文件夹不存在,SSH 客户端将退出并显示关于 unix_listener 抱怨文件或路径不存在的特定错误。

unix_listener: cannot bind to path /home/fred/.ssh/controlmasters/[email protected]:22.EOWsvm5xFt30O6CB: No such file or directory

选项 -O ssh(1) 可用于使用相同的快捷方式配置来管理连接。要取消所有现有连接(包括主连接),请使用“exit”而不是“stop”。

$ ssh -O check machine1
Master running (pid=14379)
$ ssh -O stop machine1
Stop listening request sent
$ ssh -O check machine1
Control socket connect(/Users/Username/.ssh/sockets/machine1): No such file or directory

在该示例中,首先检查连接的状态。然后告知主连接不再接受进一步的多路复用请求,最后我们再次检查是否有可用的控制套接字。

手动建立多路复用连接

[编辑 | 编辑源代码]

多路复用会话需要一个控制主控来连接。运行时参数 -M-S 分别对应于 ControlMasterControlPath。因此,首先使用 -M 建立初始主控连接,同时使用 -S 提供控制套接字的路径。

$ ssh -M -S /home/fred/.ssh/controlmasters/[email protected]:22 server.example.org

然后在其他终端中建立后续的多路复用连接。它们使用 ControlPath-S 指向控制套接字。

$ ssh -S /home/fred/.ssh/controlmasters/[email protected]:22 server.example.org

注意,控制套接字被命名为“[email protected]:22”,或 %r@%h:%p,以尝试使名称唯一。组合 %r、%h 和 %p 代表远程用户名、远程主机和远程主机的端口。控制套接字应该被赋予唯一的名称。

多个 -M 选项将 ssh(1) 置于 主控 模式,并在接受从属连接之前需要确认。这与 ControlMaster=ask 相同。两者都需要 X 来请求确认。

以下是如何为主机 server.example.org 建立主控连接,并设置为要求确认新的多路复用会话。

$ ssh -MM -S ~/.ssh/controlmasters/%r@%h:%p server.example.org

以下是如何建立后续的多路复用连接。

$ ssh -S ~/.ssh/controlmasters/%r@%h:%p server.example.org

可以使用 -O check 查询控制主控连接的状态,这将告知它是否正在运行。

$ ssh -O check -S ~/.ssh/controlmasters/%r@%h:%p server.example.org

如果控制会话已停止,即使仍然存在正在运行的多路复用会话,查询也会返回关于“没有这样的文件或目录”的错误,因为套接字已经消失。

或者,除了将 -M-S 作为运行时选项之外,还可以使用 -o 将配置选项完全拼写出来,以便在确定之后更容易转移到客户端配置文件。

以下方法通过首先启动控制主控来将配置选项设置为运行时参数。

$ ssh -o "ControlMaster=yes" -o "ControlPath=/home/fred/.ssh/controlmasters/%r@%h:%p" server.example.org

然后,后续会话将通过控制路径末端的套接字连接到控制主控。

$ ssh -o "ControlPath=/home/fred/.ssh/controlmasters/%r@%h:%p"  server.example.org

当然,所有这些都可以放入 ssh_config(5) 中,如上一节所示。从 6.7 开始,%r@%h:%p 和其变体组合可以被 %C 替换,%C 本身会从 %l%h%p%r 的串联中生成一个 SHA1 哈希。

$ ssh -S ~/.ssh/controlmasters/%C server.example.org

这样做有两个优势。一个是哈希可以比组合元素更短,同时仍然可以唯一地识别连接。另一个是它会混淆连接信息,否则这些信息会在套接字的名称中显示。

结束多路复用连接

[编辑 | 编辑源代码]

结束多路复用会话的一种方法是退出所有相关的 SSH 会话,包括控制主机。如果控制主机已使用ControlPersist放置在后台,则需要使用-O和“stop”或“exit”来停止它。这也需要知道控制套接字的完整路径和文件名,如在创建主会话时使用的那样,如果它没有在ssh_config(5)的快捷方式中定义。

$ ssh -O stop server1
$ ssh -O stop -S ~/.ssh/controlmasters/%C server1.example.org

多路复用命令-O stop将优雅地关闭多路复用。发出该命令后,控制套接字将被删除,并且不再接受该主机的任何新的多路复用会话。允许现有连接继续,并且主连接将持续存在,直到最后一个多路复用连接关闭。

相反,多路复用命令-O exit将删除控制套接字并立即终止所有现有连接。

同样,指令ControlPersist也可以设置为在一段时间的不使用后超时。那里的时间间隔以sshd_config(5)中列出的时间格式编写,或者如果未指定单位,则默认为秒。如果在指定时间内没有客户端连接,它将导致主连接自动关闭。

Host server1
        HostName server1.example.org
        ControlPath ~/.ssh/controlmasters/%C
        ControlMaster yes
        ControlPersist 2h

上面的示例让控制主机在 2 小时的空闲时间后超时。应该谨慎使用持久控制套接字。能够读取和写入控制套接字的用户可以在没有进一步身份验证的情况下建立新连接。

多路复用选项

[编辑 | 编辑源代码]

配置会话多路复用的值可以在特定于用户的ssh_config(5)、全局/etc/ssh/ssh_config中设置,或者在从 shell 或脚本运行时使用参数设置。当ControlMaster设置为“yes”时,可以通过明确将其设置为“no”来覆盖运行时参数中的ControlMaster,以重用现有主机。

$ ssh -o "ControlMaster=no" server.example.org

ControlMaster接受五个不同的值:“no”、“yes”、“ask”、“auto”和“autoask”。

  • “no”是默认值。新会话不会尝试连接到已建立的主会话,但其他会话仍然可以通过明确连接到现有套接字来进行多路复用。
  • “yes”每次都会创建一个新的主会话,除非明确覆盖。新的主会话将侦听连接。
  • “ask”每次都会创建一个新的主机,除非覆盖,该主机将侦听连接。如果被覆盖,ssh-askpass(1)将在 X 中弹出一个消息,要求主会话所有者批准或拒绝请求。如果拒绝请求,则正在创建的会话将恢复为常规的独立会话。
  • “auto”会自动创建一个主会话,但如果已经有主会话可用,后续会话会自动进行多路复用。
  • “autoask”自动假定,如果存在主会话,后续会话应该进行多路复用,但在添加会话之前先询问。

拒绝的连接将记录到主会话。

Host * 
        ControlMaster ask

ControlPath可以是固定字符串,也可以包含ssh_config(5)的 TOKENS 部分中描述的几个预定义变量中的任何一个。%L用于本地主机名的第一个组件,%l用于完整的本地主机名。%h是目标主机名,%n是原始目标主机名,%p是远程服务器上的目标端口。%r用于远程用户名,%u用于运行ssh(1)的用户。或者它们可以组合为%C,它是从%l%h%p%r生成的 SHA1 哈希。

Host * 
        ControlMaster ask 
        ControlPath ~/.ssh/controlmasters/%C

ControlPersist接受“yes”、“no”或时间间隔。如果给出了时间间隔,则默认为秒。单位可以将时间延长到分钟、小时、天、周或组合。如果为“yes”,主连接将无限期地保留在后台。

Host * 
        ControlMaster ask 
        ControlPath ~/.ssh/controlmasters/%C
        ControlPersist 10m

事后端口转发

[编辑 | 编辑源代码]

可以请求端口转发,而无需建立新连接。在这里,我们使用-L将本地主机上的端口 8080 转发到远程主机上的端口 80。

$ ssh -O forward -L 8080:localhost:80 -S ~/.ssh/controlmasters/[email protected]:22  server.example.org

使用-R可以对远程转发进行相同的操作。转义序列~C不可用于多路复用会话,因此-O forward是唯一可以在运行时添加端口转发的方法。

可以使用-O cancel取消端口转发,而无需关闭任何会话。

$ ssh -O cancel -L 8080:localhost:80 -S ~/.ssh/controlmasters/[email protected]:22  server.example.org

取消使用的语法与转发使用的语法完全相同。但是,目前无法查找正在转发的端口以及它们是如何转发的。

关于多路复用的其他说明

[编辑 | 编辑源代码]

永远不要将任何公开可访问的目录用于控制路径套接字。将这些套接字放在其他地方的目录中,只有你的帐户可以访问该目录。例如,~/.ssh/socket/将是一个安全得多的选择,而/tmp/将是一个糟糕的选择。

在需要保持连接的情况下,始终可以将多路复用与其他选项(如-f-N)组合使用,以使控制主机在连接后进入后台并且不加载 shell。

sshd_config(5)中,指令MaxSessions指定每个网络连接允许的最大打开会话数。当通过单个 TCP 连接进行多路复用 ssh 会话时,将使用此选项。将MaxSessions设置为 1 会禁用多路复用,将其设置为 0 会完全禁用登录/shell/子系统会话。默认值为 10。MaxSessions指令也可以在Match条件块下设置,以便不同的条件具有不同的设置。

阻止多路复用的错误

[编辑 | 编辑源代码]

用于保存控制套接字的目录必须位于实际允许创建套接字的文件系统上。AFS 是一个不支持的例子,一些 HFS+ 的实现也是如此。如果尝试在不允许创建套接字的文件系统上创建套接字,将会出现以下类似的错误。

$ ssh -M -S /home/fred/.ssh/mux 192.0.2.57
muxserver_listen: link mux listener /home/fred/.ssh/mux.vjfeIFFzHnhgHoOV => /home/fred/.ssh/mux: Operation not permitted

在尝试在 OverlayFS 文件系统(通常与 Docker 一起使用)上创建 Unix 域套接字时,也遇到了类似的问题,在 Linux 4.7 内核[2]之前。

如果无法重新配置文件系统以允许套接字,则唯一的其他选择是将控制路径套接字放置在支持创建套接字的文件系统上的其他位置。

观察多路复用

[编辑 | 编辑源代码]

可以进行一些粗略的测量,以显示多路复用连接与一次性连接之间的差异,如上表和图所示。

测量 TCP 连接数

[编辑 | 编辑源代码]

上表 1 和 2 使用来自netstat(8)awk(1)的输出,以显示与 SSH 连接数相对应的 TCP 连接数。

#!/bin/sh
netstat -nt | awk 'NR == 2'

ssh -f server.example.org sleep 60
echo # one connection
netstat -nt | awk '$5 ~ /:22$/'

ssh -f server.example.org sleep 60
echo # two connections
netstat -nt | awk '$5 ~ /:22$/'

ssh -f server.example.org sleep 60
echo # three connections
netstat -nt | awk '$5 ~ /:22$/'

echo Table 1

以及

#!/bin/sh
netstat -nt | awk 'NR == 2'

ssh -f -M -S ~/.ssh/demo server.example.org sleep 60
echo # one connection
netstat -nt | awk '$5 ~ /:22$/'

ssh -f -S ~/.ssh/demo server.example.org sleep 60 &
echo # two connections
netstat -nt | awk '$5 ~ /:22$/'

ssh -f -S ~/.ssh/demo server.example.org sleep 60 &
echo # three connections
netstat -nt | awk '$5 ~ /:22$/'

echo Table 2

如果需要更多延迟,可以增加sleep(1)周期。当连接处于活动状态时,还可以使用ps(1)(例如,使用ps uwx)在服务器上查看进程,作为验证是否确实建立了多个连接的方法。

测量响应时间

[编辑 | 编辑源代码]

测量响应时间需要首先使用代理设置密钥,以便代理处理身份验证并消除身份验证作为延迟来源。如果需要,请参阅有关密钥的部分。以下所有响应时间测试都将依赖于使用密钥进行身份验证。

对于一次性连接,只需添加time(1)以检查访问需要多长时间。

$ time ssh -i ~/.ssh/rsakey server.example.org true

对于多路复用连接,必须建立主控会话。然后,后续连接将显示速度提高。

$ ssh -f -M -S ~/.ssh/demo -i ~/.ssh/rsakey server.example.org 
$ time ssh  -S ~/.ssh/demo -i ~/.ssh/rsakey server.example.org true
$ time ssh  -S ~/.ssh/demo -i ~/.ssh/rsakey server.example.org true

这些响应时间是近似的,但一次性连接与多路复用连接之间的差异仍然足够大,可以观察到。

保持会话打开

[编辑 | 编辑源代码]

您可能还想在不活动时保持会话打开。您可以使用ServerAliveIntervalServerAliveCountMax选项进行配置,有关详细信息,请参阅ssh_config(5)

使用 sslh 多路复用 HTTPS 和 SSH

[编辑 | 编辑源代码]

另一种多路复用方式是,多个协议通过同一个端口传输。 sslh 针对 SSL 和 SSH 实现了这种方式。它可以识别传入的连接类型,并将其转发到相应的服务。因此,它允许服务器通过同一个端口接收 HTTPS 和 SSH 连接,从而在某些情况下可以通过限制性防火墙连接到服务器。它不会隐藏 SSH。即使使用scanssh(1) 对监听端口进行快速 SSH 扫描,也会显示 SSH 在那里。请注意,此方法仅适用于更简单的包过滤器,例如 PF 或 nftables(8) 及其前端 firewalld 和 UFW,这些过滤器根据目标端口进行过滤,不会欺骗协议分析和应用程序层过滤器,例如 Zorp,这些过滤器根据实际使用的协议进行过滤。以下是安装 sslh(8) 的四个步骤

  • 首先安装您的 Web 服务器,并将其配置为接受 HTTPS 请求。确保它仅在本地主机上监听 HTTPS。如果它在非标准端口(例如 2443 而不是 443)上,则可以帮助您。
  • 接下来,将您的 SSH 服务器设置为在本地主机上接受端口 22 的连接。它实际上可以是任何端口,但端口 22 是 SSH 的标准端口。
  • 接下来,创建一个非特权用户来运行 sslh(8)。下面的示例使用“sslh”作为非特权帐户的用户。
  • 最后,安装并启动 sslh(8),使其监听端口 443,并将 HTTPS 和 SSH 转发到本地主机的相应端口。请将您的机器的外部 IP 地址替换掉。可执行文件的实际名称和路径可能因系统而异
$ /usr/local/sbin/sslh-fork -u sslh -p xx.yy.zz.aa:443 --tls 127.0.0.1:2443 --ssh 127.0.0.1:22

另一种选择是使用配置文件来使用 sslh(8),而不是在运行时传递任何参数。安装软件包时,应该至少有一个示例配置文件,basic.cfgexample.cfg。完成的配置文件应该看起来像这样

user: "sslh";
listen: ( { host: "xx.yy.zz.aa"; port: "443" } );
on-timeout: "ssl";
protocols:
(
   { name: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
   { name: "ssl"; host: "localhost"; port: "2443"; probe: "builtin"; }
);

注意引号、逗号和分号。

如果使用旧版本的 SSH 与现已弃用的 TCP Wrappers 结合使用,如 hosts_access(5) 中所述,则选项 service: 可用于提供它们所需的服务的名称。

    { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },

如果不使用 TCP Wrappers(这应该是大多数情况),则不需要 service:

运行时参数会覆盖任何可能存在的配置文件设置。 sslh(8) 支持 HTTP、SSL、SSH、OpenVPN、tinc 和 XMPP 协议开箱即用。但是,实际上可以使用任何可以通过正则表达式模式匹配识别的协议。有两种变体的 sslh,一个带分叉版本的 (sslh 或 sslh-fork) 和一个单线程版本 (sslh-select)。有关更多详细信息,请参阅项目网站:https://www.rutschle.net/tech/sslh/README.html


参考资料

[编辑 | 编辑源代码]
  1. "OpenSSH 3.9 版本说明". OpenSSH. 2004-08-18. 检索于 2018-12-16.
  2. Szeredi, Miklos (2016-06-16). "[GIT PULL overlayfs 修复 4.7-rc3"]. https://lkml.org/lkml/2016/6/16/87. 检索于 2018-10-05. 

 

华夏公益教科书