I have ran into trouble while starting SSH tunnel from HTTP RPC server written in Python.
There is a simple HTTP RPC server written in Python based on Python's BaseHTTPServer. As a part of one of the services I would like to start a SSH tunnel from the RPC server to a remote machine. I used 开发者_JAVA百科os.system to start the SSH tunnel in the Python script invoked by the RPC call
os.system("ssh -f -n -N -L 127.0.0.1:%d:localhost:%d user@%s" % (6800, 9000, "remote.machine"))
At first sight all seems to be well as the tunnel is started and I can use it, but there is one thing I noticed. In addition to listening on the port 6800 SSH started listening on port 8001 as well (the port that the HTTP RPC server runs on).
Here is output of lsof regarding the RPC server and SSH:
rpc.py 27763 usern 5u IPv4 102130428 TCP 127.0.0.1:8001 (LISTEN)
ssh 1951 usern 14u IPv4 102149728 TCP 127.0.0.1:6800 (LISTEN)
ssh 1951 usern 5u IPv4 102130428 TCP 127.0.0.1:8001 (LISTEN)
Everything works until RPC server's restart. During restart the RPC server is forced to close his connection to the listening socket but the SSH's connection remains open and RPC server can not start on the same port again.
It seems that the SSH tunnel also somehow associates itself with the fd of the RPC server's listening socket.
Could anybody give hints how to set up the SSH tunnel from the script with it only listening on the supposed port (6800 in this example).
Instead of os.system(), try using subprocess.Popen() and set close_fds=True (available in Python 2.4 and higher).
import subprocess
subprocess.Popen("ssh -f -n -N -L 127.0.0.1:%d:localhost:%d user@%s" % (6800, 9000, "remote.machine"), shell=True, close_fds=True)
In theory, I believe you should also be able to use fcntl to set the listening socket's file descriptor to close-on-exec (FD_CLOEXEC) prior to calling os.system().
I suspect this has something to do with forking and then telling ssh to detach from the calling process ("-f").
Also, not sure if this will help, but you should really use the subprocess module if your python is modern enough to have it.
Have you considered using paramiko for your SSH? (depends on PyCrypto, but otherwise pure Python)
That'd make managing the connection simpler and, depending on how your server deals with sockets, you may just be able to set up a paramiko Channel and then let the server treat it as it would any other listening socket.
(in paramiko, Transport objects represent the basic SSH2 connections and, for each Transport, you can create multiple Channel objects, each of which acts as a drop-in replacement for a regular Python socket)
Here are some resources in case you want to take a look:
- SFTP in Python: Paramiko (Provides a simple example of getting a Transport set up)
- API Docs: paramiko.Transport (Direct link to the docs for Transport.open_channel)
精彩评论