Клиент OpenSSH блокируется навсегда, если stderr перенаправляется в stdout

Клиент OpenSSH блокируется навсегда, если stderr перенаправляется в stdout

Кажется, проблема может быть в openssh. В bashоболочке, если я перенаправляюсь stderrна stdoutнее, она блокируется навсегда, поэтому мне приходится 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(для главного процесса управления — stdin и stdout в2010, stderr в2016; для процесса сеанса — stdout в2017), но если вам приходится запускать старый ssh ​​или нужно, чтобы он перестал цепляться за stderr, единственным «решением» может быть использование хака, 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то, что нужно завершить до того, как оболочка доберется до echo. Это "блокировка навсегда", потому что catжизни.

Есть ли обходной путь?

В Bash я бы использовал подстановку процесса для запуска cat, который не блокируется. С catубранным с пути это тогда легко сделать, echo OKтолько если это действительно нормально (с &&или $?). Пример:

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

Теперь catработает до и после sshперехода в фоновый режим, но скрипт продолжает работу сразу после sshэтого (или сразу после sshсбоя без перехода в фоновый режим).

Связанный контент