如果 stderr 重定向到 stdout,Openssh 用戶端將永遠阻塞

如果 stderr 重定向到 stdout,Openssh 用戶端將永遠阻塞

看起來可能有問題openssh。在bashshell 中,如果我重定向stderrstdout它,它就會永遠阻塞,所以我必須KeyboardInterrupt

$ ssh -fTNF './config' -MS './sockd5/ctrl_socket' -i './keys/id_rsa' -l 'root' -p '22' 'example.com' 2>&1 | cat -A; echo OK
ControlSocket ./sockd5/ctrl_socket already exists, disabling multiplexing^M$
^C

沒有重定向的相同命令可以正常工作:

$ ssh -fTNF './config' -MS './sockd/ctrl_socket' -i './keys/id_rsa' -l 'root' -p '22' 'example.com' | cat -A; echo OK
ControlSocket ./sockd/ctrl_socket already exists, disabling multiplexing
OK

為什麼會發生這種情況?有解決方法嗎?

答案1

ssh -f連接並驗證到主機後「進入背景」時,它將繼續保留其原始 stdin、stdout 和 stderr 的開啟句柄,因此如果這些句柄透過管道連接到其他進程(如其 stdout + stderr在您的範例中cat -A),它將具有使這些進程保持活動狀態的效果,即使它們不再需要。

ssh透過呼叫來守護程式自身daemon(3)函式庫函數,但它使用 來呼叫它noclose = 1,從而阻止它從 重定向 stdin/stderr/stdout /dev/null

這在最新版本中已部分修復openssh(對於主控制進程——標準輸入和標準輸出2010年,標準錯誤2016年;對於會話進程-標準輸出2017年),但如果您必須運行較舊的 ssh,或者需要它也停止堅持 stderr,唯一的“解決方案”可能是使用 hack LD_PRELOAD,用一個包裝器覆蓋該daemon(3)函數,該包裝器用noclose = 0.

$ cat daemon-force-close.c
#define _GNU_SOURCE
#include <unistd.h>
#include <dlfcn.h>
#include <err.h>

int daemon(int nochdir, int noclose){
        static int (*orig)(int, int);
        if(!orig && !(*(void**)&orig = dlsym(RTLD_NEXT, "daemon")))
                errx(1, "%s", dlerror());
        return orig(nochdir, 0);
}

$ cc -shared -Wall -O2 daemon-force-close.c -ldl -o daemon-force-close.so

$ LD_PRELOAD=./daemon-force-close.so \
   ssh -Nf dummy@localhost -MS ./ctlsock 2>&1 | cat -A
dummy@localhost's password:
$
[no Ctrl-C needed]
$ ssh -S ~/w/c/ctlsock dummy@localhost
Last login: Tue May 28 21:04:26 2019 from ::1
...

答案2

為什麼會發生這種情況?

cat在 shell 到達 之前需要終止它echo。它“永遠阻塞”,因為cat生命。

有解決方法嗎?

在 Bash 中,我會使用進程替換來運行cat,不會阻塞。有了catout of the way,echo OK只有當它確實沒問題時(使用&&$?)才很容易。例子:

ssh -f … > >(cat -A) 2>&1 && echo OK; echo "The script goes on."

現在在進入背景cat之前和之後都可以工作ssh,但是腳本一旦ssh執行就會繼續(或一旦ssh失敗而不進入背景)。

相關內容