I have next code, it will do next:
- subprocess for
ssh -f -M
to let ssh launch a shared socket in background - As above is in background, so for the second ssh connection we could just reuse the socket
/tmp/control-channel
to connect ssh server without password.
test.py:
import subprocess
import os
import sys
import stat
ssh_user = "my_user" # change to your account
ssh_passwd = "my_password" # change to your password
try:
os.remove("/tmp/control-channel")
except:
pass
# prepare passwd file
file = open("./passwd","w")
passwd_content = f"#!/bin/shnecho {ssh_passwd}"
file.write(passwd_content)
file.close()
os.chmod("./passwd", stat.S_IRWXU)
# setup shared ssh socket, put it in background
env = {'SSH_ASKPASS': "./passwd", 'DISPLAY':'', 'SSH_ASKPASS_REQUIRE':'force'}
args = ['ssh', '-f', '-o', 'LogLevel=ERROR', '-x', '-o', 'ConnectTimeout=30', '-o', 'ControlPersist=300', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-o', 'ServerAliveInterval=15', '-MN', '-S', '/tmp/control-channel', '-p', '22', '-l', ssh_user, 'localhost']
process = subprocess.Popen(args, env=env,
stdout=subprocess.PIPE,
# stderr=subprocess.STDOUT, # uncomment this line to enable stderr will make subprocess hang
stdin=subprocess.DEVNULL,
start_new_session=True)
sout, serr = process.communicate()
print(sout)
print(serr)
# use shared socket
args2 = ['ssh', '-o', 'LogLevel=ERROR', '-o', 'ControlPath=/tmp/control-channel', '-p', '22', '-l', ssh_user, 'localhost', 'uname -a']
process2 = subprocess.Popen(args2,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.DEVNULL)
content, _ = process2.communicate()
print(content)
execution:
$ python3 test.py
b''
None
b'Linux shmachine 4.19.0-21-amd64 #1 SMP Debian 4.19.249-2 (2022-06-30) x86_64 GNU/Linuxn'
So far so good, just if I uncomment stderr=subprocess.STDOUT
in the first subprocess, it will hang:
$ python3 test.py
^CTraceback (most recent call last):
File "test.py", line 29, in <module>
sout, serr = process.communicate()
File "/usr/lib/python3.7/subprocess.py", line 926, in communicate
stdout = self.stdout.read()
KeyboardInterrupt
I wonder what’s the problem here?
My environment:
$ python3 --version
Python 3.7.3
$ ssh -V
OpenSSH_7.9p1 Debian-10+deb10u2, OpenSSL 1.1.1n 15 Mar 2022
$ cat /etc/issue
Debian GNU/Linux 10 n l
UPDATE: I saw this post which similar to my issue, but no answer.
UPDATE2: Change communicate
to wait
makes it work, but the pipe size which wait use
surely less than memory size which communicate use
, so I still wonder why I can’t make it work with communicate
.
2
Answers
I remove the stdout pipe set, just leave the stderr there to have a minimal check, and finally use strace to confirm it's the bug of old ssh which fixed in Y2020 in ssh 8.4. So python did correct behavior...
ssh -f
did not close stderr when put the ssh to background, socommunicate
can't read a EOF of stderr then hangs as next:And I confirm it with openssh commit
The answer really just hides in the python documention subprocess.Popen, and within the documention of
Popen.communicate
, reads the following:Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
Therefore, a (potentially) working solution (since I don’t really have a working SSH to test on), would be to add a
timeout
to thecommunicate
call.