我正在编写一个Python脚本,定期通过已建立的SSH隧道查询几个远程数据库。我对paramiko库比较熟悉,所以选择了这条路线。我希望保持完全使用Python,这样我就可以使用paramiko来处理密钥问题,并使用Python启动、控制和关闭SSH隧道。
这个话题在这里有一些相关的问题,但大多数答案都不完整。我的解决方案是从我找到的解决方案中拼凑出来的。
现在是问题:我能够很容易地创建第一个隧道(在单独的线程中),并进行我的DB/Python操作,但当尝试关闭隧道时,本地主机不会释放我绑定的本地端口。下面,我包含了我的源代码和每个步骤的相关netstat数据。
这个话题在这里有一些相关的问题,但大多数答案都不完整。我的解决方案是从我找到的解决方案中拼凑出来的。
现在是问题:我能够很容易地创建第一个隧道(在单独的线程中),并进行我的DB/Python操作,但当尝试关闭隧道时,本地主机不会释放我绑定的本地端口。下面,我包含了我的源代码和每个步骤的相关netstat数据。
#!/usr/bin/python
import select
import SocketServer
import sys
import paramiko
from threading import Thread
import time
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
class Handler (SocketServer.BaseRequestHandler):
def handle(self):
try:
chan = self.ssh_transport.open_channel('direct-tcpip', (self.chain_host, self.chain_port), self.request.getpeername())
except Exception, e:
print('Incoming request to %s:%d failed: %s' % (self.chain_host, self.chain_port, repr(e)))
return
if chan is None:
print('Incoming request to %s:%d was rejected by the SSH server.' % (self.chain_host, self.chain_port))
return
print('Connected! Tunnel open %r -> %r -> %r' % (self.request.getpeername(), chan.getpeername(), (self.chain_host, self.chain_port)))
while True:
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
chan.close()
self.request.close()
print('Tunnel closed from %r' % (self.request.getpeername(),))
class DBTunnel():
def __init__(self,ip):
self.c = paramiko.SSHClient()
self.c.load_system_host_keys()
self.c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.c.connect(ip, username='someuser')
self.trans = self.c.get_transport()
def startTunnel(self):
class SubHandler(Handler):
chain_host = '127.0.0.1'
chain_port = 5432
ssh_transport = self.c.get_transport()
def ThreadTunnel():
global t
t = ForwardServer(('', 3333), SubHandler)
t.serve_forever()
Thread(target=ThreadTunnel).start()
def stopTunnel(self):
t.shutdown()
self.trans.close()
self.c.close()
虽然最终我会使用stopTunnel()类型的方法,但我意识到代码并不完全正确,而更多是试图关闭隧道并测试结果的实验。
当我首次调用create DBTunnel对象并调用startTunnel()时,netstat会产生以下结果:
tcp4 0 0 *.3333 *.* LISTEN
tcp4 0 0 MYIP.36316 REMOTE_HOST.22 ESTABLISHED
tcp4 0 0 127.0.0.1.5432 *.* LISTEN
一旦我调用stopTunnel()方法,甚至删除DBTunnel对象本身..我将一直保留这个连接,直到我完全退出Python,然后我假设垃圾收集器会处理它:
tcp4 0 0 *.3333 *.* LISTEN
希望找出为什么这个开放的套接字会独立于DBConnect对象而挂起,并学习如何从我的脚本中正确关闭它。如果我在完全退出Python之前尝试将不同的连接绑定到不同的IP使用相同的本地端口(time_wait不是问题),那么我会遇到臭名昭著的绑定错误48地址已被使用。谢谢提前 :)