2010-12-10 179 views
4

我正在研究一個python腳本,每隔一段時間就通過建立的ssh隧道查詢一些遠程數據庫。我對paramiko圖書館非常熟悉,所以這是我選擇的路線。我寧願將它保留在完整的python中,以便我可以使用paramiko來處理關鍵問題,以及使用python來啓動,控制和關閉ssh隧道。Paramiko SSH隧道關閉問題

在這裏有一些關於這個話題的相關問題,但其中大多數在答案中似乎不完整。以下我的解決方案是迄今爲止發現的解決方案的混合。

現在的問題:我能夠很容易地創建第一個隧道(在一個單獨的線程中),並做我的DB/python的東西,但是當試圖關閉隧道localhost不會釋放本地端口我綁定到了。下面,我通過流程的每一步都包含了我的源代碼和相關的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()類型的方法,我已經意識到,代碼是不完全正確的,但更多的努力得到正確的隧道關閉的實驗和測試我的結果。

當我首先調用創建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對象itself..I'm左與此相聯繫,直到我退出蟒蛇一起,我認爲是垃圾收集什麼照顧它:

tcp4  0  0 *.3333     *.*     LISTEN 

這將是很好找出爲什麼這個開放插座掛在數據庫連接對象的獨立,以及如何從內部正確關閉它我的腳本。如果我在完全退出python之前嘗試使用相同的本地端口綁定到不同IP的不同連接(time_wait不是問題),那麼我會得到臭名昭着的bind err 48地址。在此先感謝:)

+1

上面的代碼演示了一個錯誤在原始l paramiko轉發示例代碼,這是self.request.getpeername()導致一個錯誤的文件描述符異常,因爲它是在請求關閉後調用的。修正是在這裏:https://github.com/paramiko/paramiko/pull/36 – 2011-12-29 23:06:58

回答

1

看來SocketServer的關閉方法沒有正確關閉/關閉套接字。在我的代碼中進行了以下更改後,我仍然可以訪問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() 
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() 
1

請注意,您沒有按照演示代碼所示的方式進行Subhandler的破解。評論是錯誤的。處理程序可以訪問其服務器的數據。在處理程序中,您可以使用self.server.instance_data

如果使用下面的代碼,在你的處理器,你可以使用

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

+1,好趕上。正如[文檔](http://docs.python.org/2/library/socketserver.html#requesthandler-objects)所述:「有幾個實例屬性可用... [包括]服務器實例爲self.server ,以防萬一[RequestHandler]需要訪問每個服務器的信息。「 – Air 2013-12-09 23:39:09