如果 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

为什么会发生这种情况?

在 shell 到达 之前,需要cat终止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失败而不进入后台)。

相关内容