跳轉到內容

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/fred@machine1.example.org: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/fred@server.example.org:22 server.example.org

然後在其他終端中建立後續的多路複用連線。它們使用 ControlPath-S 指向控制套接字。

$ ssh -S /home/fred/.ssh/controlmasters/fred@server.example.org:22 server.example.org

注意,控制套接字被命名為“fred@server.example.org: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/fred@server.example.org:22  server.example.org

使用-R可以對遠端轉發進行相同的操作。轉義序列~C不可用於多路複用會話,因此-O forward是唯一可以在執行時新增埠轉發的方法。

可以使用-O cancel取消埠轉發,而無需關閉任何會話。

$ ssh -O cancel -L 8080:localhost:80 -S ~/.ssh/controlmasters/fred@server.example.org: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. 

 

華夏公益教科書