Paramiko SSH 隧道关闭问题 [英] Paramiko SSH Tunnel Shutdown Issue

查看:260
本文介绍了Paramiko SSH 隧道关闭问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个 python 脚本,以每隔一段时间通过已建立的 ssh 隧道查询一些远程数据库.我对 paramiko 库相当熟悉,所以这是我选择的路线.我更愿意将其保留在完整的 python 中,以便我可以使用 paramiko 来处理关键问题,以及使用 python 来启动、控制和关闭 ssh 隧道.

I'm working on a python script to query a few remote databases over an established ssh tunnel every so often. I'm fairly familiar with the paramiko library, so that was my choice of route. I'd prefer to keep this in complete python so I can use paramiko to deal with key issues, as well as uses python to start, control, and shutdown the ssh tunnels.

这里有一些关于此主题的相关问题,但其中大部分的答案似乎都不完整.我下面的解决方案是我目前找到的解决方案的组合.

There have been a few related questions around here about this topic, but most of them seemed incomplete in answers. My solution below is a hacked together of the solutions I've found so far.

现在问题来了:我可以很容易地(在单独的线程中)创建第一个隧道并执行我的 DB/python 操作,但是当尝试关闭隧道时,本地主机不会释放本地端口我绑定到.下面,我在过程的每个步骤中都包含了我的源和相关的 netstat 数据.

Now for the problem: I'm able to create the first tunnel quite easily (in a separate thread) and do my DB/python stuff, but when attempting to close the tunnel the localhost won't release the local port I binded to. Below, I've included my source and the relevant netstat data through each step of the process.

#!/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() 类型的方法,但我意识到代码并不完全正确,但更多的是尝试让隧道正确关闭并测试我的结果的实验​​.

Although I will end up using a stopTunnel() type method, I've realize that code isn't entirely correct, but more so an experimentation of trying to get the tunnel to shutdown properly and test my results.

当我第一次调用 create DBTunnel 对象并调用 startTunnel() 时,netstat 产生以下结果:

When I first call create the DBTunnel object and call startTunnel(), netstat yields the following:

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,我假设垃圾收集器会处理它:

Once I call stopTunnel(), or even delete the DBTunnel object itself..I'm left with this connection until I exit python all together, and what I assume to be the garbage collector takes care of it:

tcp4       0      0 *.3333                 *.*                    LISTEN

很高兴弄清楚为什么这个打开的套接字独立于 DBConnect 对象悬而未决,以及如何从我的脚本中正确关闭它.如果我在完全退出 python 之前尝试使用相同的本地端口将不同的连接绑定到不同的 IP(time_wait 不是问题),那么我会得到臭名昭著的 bind err 48 地址.提前致谢:)

It would be nice to figure out why this open socket is hanging around independent of the DBConnect object, and how to close it properly from within my script. If I try and bind a different connection to different IP using the same local port before completely exiting python (time_wait is not the issue), then I get the infamous bind err 48 address in use. Thanks in advance :)

推荐答案

请注意,您没有执行演示代码中所示的 Subhandler hack.评论是错误的.处理程序确实可以访问其服务器的数据.在处理程序中,您可以使用 self.server.instance_data.

Note that you don't have do the Subhandler hack as shown in the demo code. The comment is wrong. Handlers do have access to their Server's data. Inside a handler you can use self.server.instance_data.

如果您使用以下代码,在您的处理程序中,您将使用

If you use the following code, in your Handler, you would use

  • 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()

这篇关于Paramiko SSH 隧道关闭问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆