Repository navigation
Accessing process.stdout terminates SSH connections #13278
Description
- Version: v7.9.0
- Platform: Linux dev-obelisk-1 2.6.18-274.el5 deps: update openssl to 1.0.1j #1 SMP Fri Jul 8 17:36:59 EDT 2011 x86_64 x86_64 x86_64 GNU/Linux (RHEL 5.7)
- Subsystem: Process
If this is of any importance, the local machine where the SSH connection was opened from is running MacOS 10.12.4. The info above applies to the remote machine where I connect to via SSH.
I am trying to write a library which serves to execute remote commads via SSH. In my first example I was using a Node C++ wrapper around libssh, but after researching this issue for a long time I am down to a very simple node.js sample program which demonstrates the error.
Given the following test program:
const spawn = require('child_process').spawn;
const bat = spawn('ssh', ['user@host']);
setTimeout(() => {
bat.stdin.write('node test.js\n');
setTimeout(() => bat.stdin.write('pwd -P\n'), 2000);
}, 2000);With the following contents of test.js on the remote host:
if (process.stdout) ; // only this line
The program will disconnect the SSH session after the node test.js command is issued, resulting in the scheduled pwd -P to fail and throw an exception.
If you change the contents of test.js to just if (process) ; (or anything else not "touching" process.stdout/process.stderr), the test program will run properly and execute all remote commands without problems.
I have tried several commands, and none of the other commands I used (cp, cd, mkdir, ls, git, ssh, scp, ...) failed in this way, only running node.js scripts which use process.stdout/stderr seem to fail.
This is pretty unfortunate, because for my actual implementation (automatic build/deploy system) I must execute npm install, which however breaks the whole application because of the issue described above.
I have researched this somewhat in detail here: https://stackoverflow.com/questions/43972863/ssh-npm-seems-to-break-ssh-non-pty-shells and also here: https://stackoverflow.com/questions/43784514/channel-in-libssh-gets-closed-for-no-obvious-reason?noredirect=1#comment74611459_43784514 (this one includes a working minimal C++ program using libssh and also the SSH protocol level packet/debug output which might help in investigating the issue.
For me, as a non-SSH expert, it looks like that Node.js is trying to use a TTY, while the SSH connection it is running on doesn't have a TTY. Therefore the SSH server terminated the connection. So please note, the error happens only in this non-tty enviroment. Of course, if you open your terminal application and SSH to some machine, executing the above node script will work flawlessly.
Activity
gireeshpunathil commentedon May 29, 2017
Unable to reproduce with the steps you provided, on similar OS version and same node version.
Can you try the following code:
This way, you can provide commands repeatedly and interactively.
Also, in your failing scenario, pass --trace option to node to see how and why it is failing. (you need to capture the stdout of the child)
patrickjane commentedon May 29, 2017
On which remote linux are you testing? I was able to check the following versions of redhat enterprise linux:
Output with your example code (command
node test.js(+enter) followed byls(+enter)):I fail to post the trace output for it is over 65536 characters, and attaching it as a file shows "Something went really wrong, and we can't process that file". So I uploaded it here:
http://filehorst.de/d/bCjDbncp
bnoordhuis commentedon May 29, 2017
I expect that is a bug in ssh, not node. RHEL 5 ships openssh 4.3 from 2006. It has a huge list of known issues.
gireeshpunathil commentedon May 29, 2017
Mine is RHEL 6.6
Unfortunately, I don't have a RHEL 5.* to test and validate this.
The trace file is good, and proves that the child node exited gracefully.
So as @bnoordhuis pointed out, this can be a bug in the SSH itself. Probably
const bat = spawn('strace', ['-f', 'ssh', 'user@host']);can confirm this?
patrickjane commentedon May 29, 2017
It will give me this, although I fail to interpret it correctly. The only thing which got my attention was
ioctl(0, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fffc6f2d5f0) = -1 ENOTTY (Inappropriate ioctl for device)Full output:
gireeshpunathil commentedon May 29, 2017
Nothing obvious found other than the ones which you pointed out. Will give it another try later.
gibfahn commentedon May 29, 2017
I'm going to close this as it sounds like it's a RHEL 5.7 problem, and we don't support 5.7 (or even 5.11, which is I think the only RHEL 5 version that still has extended support from Red Hat). Node 8 won't run at all on RHEL 5.x, and it's quite possible that Node 6 will cease to work on it at some point.
I assume upgrading to RHEL 7.3 isn't an option for you, so feel free to continue updating this thread.
I see you also raised npm/npm#16608.
patrickjane commentedon May 30, 2017
Yes, unfortunately I am bound to use RHEL 5.7 by customer requirement, so problably there is nothing that I can do.
gireeshpunathil commentedon May 30, 2017
Here is the sequence of SSH protocol as understood by me, based on a modern SSH
The failing sequence which you showed above not only falling into an identifiable pattern, but also shows bad symptoms (close and ioctl calls) without evidence captured in the trace - probably decisions taken at the app (ssh) level not visible in the system calls.
Can you reverse the direction (linux as source and mac as target host) and check?
patrickjane commentedon May 30, 2017
This is RHEL 5.7 -> RHEL 6.4:
(and the connection works without disconnect)
And this is RHEL 5.7 -> MacOS
(also works without problems)
Also looks quite understandable to me, unlike the RHEL 5.7 output I posted earlier.
gireeshpunathil commentedon May 30, 2017
Looks like the old ssh closed its stream(s) in an unknown manner is how the problem has surfaced. For example, if I add a line to force close the stdin of the ssh child, I get the same sequence as that of yours:
As a last experiment, can you trace the parent, much like you did it for the child? Just to make sure that node is not doing anything unexpected?
patrickjane commentedon May 30, 2017
I don't think you got the same output. There is some stuff missing, for example
brk(0)but more importantly theioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS , 0x7fffc6f2d5f0) = -1 ENOTTY (Inappropriate ioctl for device)is missing.I am by no means an expert in neither SSH nor all the system calls, but my assumption was from the beginning (back when I thought the issue is caused by
libsshand I opened a ticket also on their bugtracker) that node is doing something which wants a TTY, but the SSH connection is not running on a TTY. And maybe the old SSH version has some kind of bad handling about this, in that it just closes the connection, while the newer versions won't close the connection.At least I find it especially interesting, that all the other remote commands (which of course also heavily use stdout/stderr) just work fine, for example
git.When tracing the parent node, I would get this:
gireeshpunathil commentedon May 30, 2017
sorry for being vague - I meant
--tracefor the parent: to make sure that we are not inadvertently closing the child (ssh)'s stream. In the abovestraceI can see that the parent is writing into fd 10 and subsequently closing it, but data from trace would provide real evidence.patrickjane commentedon May 30, 2017
Ah okay. This should be what I already posted in my first answer, now?
Anyway heres the output again, starting from when I typed the remote command:
http://filehorst.de/d/bDFfGgiu
gireeshpunathil commentedon May 30, 2017
no, I meant the parent (the script which invokes ssh). the last trace was for the child (test.js) right?
patrickjane commentedon May 30, 2017
Nope, Its
node --trace ssh.js(and ssh.js will in turn SSH to the remote and invokenode test.js). But then yeah, my first response contained the trace of the child, youre right.gireeshpunathil commentedon May 30, 2017
thanks for the clarification, will have a look