我正在研究一个python脚本,每隔一段时间就通过已建立的ssh隧道查询一些远程数据库。我对paramiko库非常熟悉,所以这是我选择的路线。我更喜欢将它保存在完整的python中,所以我可以使用paramiko来处理关键问题,以及使用python来启动,控制和关闭ssh隧道。
这里有关于这个主题的一些相关问题,但大多数问题似乎都不完整。我在下面的解决方案是我迄今为止找到的解决方案的黑客。
现在出现问题:我能够很容易地创建第一个隧道(在一个单独的线程中)并执行我的DB / python,但是当尝试关闭隧道时,localhost将不会释放本地端口I绑定到。下面,我通过流程的每个步骤包含了我的源代码和相关的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地址。在此先感谢:)
答案 0 :(得分:1)
看来SocketServer的shutdown方法没有正确关闭/关闭套接字。通过以下代码更改,我保留对SocketServer对象的访问权限并直接访问套接字以关闭它。请注意,socket.close()在我的情况下工作,但其他人可能对socket.shutdown()以及socket.close()感兴趣,如果其他资源正在访问该套接字。
[参考:socket.shutdown vs socket.close
def ThreadTunnel():
self.t = ForwardServer(('127.0.0.1', 3333), SubHandler)
self.t.serve_forever()
Thread(target=ThreadTunnel).start()
def stopTunnel(self):
self.t.shutdown()
self.trans.close()
self.c.close()
self.t.socket.close()
答案 1 :(得分:1)
请注意,您没有像演示代码中所示执行Subhandler hack。评论错了。处理程序可以访问其服务器的数据。在处理程序中,您可以使用self.server.instance_data
。
如果使用以下代码,则在Handler中使用
self.server.chain_host
self.server.chain_port
self.server.ssh_transport
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
def __init__(
self, connection, handler, chain_host, chain_port, ssh_transport):
SocketServer.ThreadingTCPServer.__init__(self, connection, handler)
self.chain_host = chain_host
self.chain_port = chain_port
self.ssh_transport = ssh_transport
...
server = ForwardServer(('', local_port), Handler,
remote_host, remote_port, transport)
server.serve_forever()
答案 2 :(得分:0)
您可能希望在生成的线程和调用者之间添加一些同步,以便在准备好之前不要尝试使用隧道。类似的东西:
from threading import Event
def startTunnel(self):
class SubHandler(Handler):
chain_host = '127.0.0.1'
chain_port = 5432
ssh_transport = self.c.get_transport()
mysignal = Event()
mysignal.clear()
def ThreadTunnel():
global t
t = ForwardServer(('', 3333), SubHandler)
mysignal.set()
t.serve_forever()
Thread(target=ThreadTunnel).start()
mysignal.wait()
答案 3 :(得分:0)
如果您要等到所有活动连接的结束或.stop()
关闭所有活动的连接,也可以尝试sshtunnel关闭隧道.stop(force=True)
的两种方法。
如果您不想使用它,可以在这里检查此逻辑的源代码:https://github.com/pahaz/sshtunnel/blob/090a1c1/sshtunnel.py#L1423-L1456