2010-12-10 4 views
4

저는 파이썬 스크립트를 사용하여 설립 된 ssh 터널을 통해 원격 데이터베이스를 쿼리합니다. Paramiko 라이브러리에 상당히 익숙하므로 경로를 선택했습니다. 필자는 완전한 파이썬에서 이것을 유지하여 paramiko를 사용하여 주요 문제를 처리하고 파이썬을 사용하여 ssh 터널을 시작, 제어 및 종료 할 수 있습니다.Paramiko SSH 터널 종료 문제

이 주제와 관련하여 여기에 몇 가지 관련 질문이 있지만 대부분 답변이 불완전한 것으로 보입니다. 아래의 내 솔루션은 지금까지 발견 한 솔루션의 해킹입니다.

이제 문제는 : 첫 번째 터널을 아주 쉽게 (별도의 스레드에서) 만들 수 있고 DB/파이썬 작업을 할 수 있지만 터널을 닫으려고 할 때 로컬 호스트는 로컬 포트를 해제하지 않습니다. 나는 묶었 다. 아래에는 프로세스의 각 단계를 통해 소스와 관련 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()를 호출, 또는 itself..I'm가 왼쪽 DBTunnel 개체를 삭제하면 이 모두 함께 I 출구 파이썬까지 연결하고, 제가 가비지 컬렉터로 가정으로 처리한다 :

tcp4  0  0 *.3333     *.*     LISTEN 

이 열려있는 소켓이 DBConnect 객체의 독립적 주위를 어슬렁 거리는 이유를 알아낼 좋은 것 , 내부에서 올바르게 닫는 방법 내 대본. 파이썬을 완전히 종료하기 전에 동일한 로컬 포트를 사용하여 다른 IP에 다른 연결을 시도하면 (time_wait은 문제가 아님), 악명 높은 바인드 오류 48 주소가 사용 중입니다. 미리 감사드립니다 :)

+1

을 사용 원래의 l paramiko는 샘플 코드를 전달한다. self.request.getpeername()은 요청이 닫힌 후에 불려지기 때문에 잘못된 파일 기술자 예외가 발생한다. 해결 방법은 다음과 같습니다. https://github.com/paramiko/paramiko/pull/36 –

답변

1

SocketServer의 종료 방법이 소켓을 올바르게 종료하거나 닫지 않은 것으로 보입니다. 아래 코드를 변경하면 SocketServer 객체에 대한 액세스가 유지되고 소켓을 직접 액세스하여 소켓을 닫을 수 있습니다. 필자의 경우 socket.close()가 작동하지만, 다른 리소스가 해당 소켓에 액세스하고 있으면 socket.close()가 이어지는 socket.shutdown()에 관심이있을 수 있습니다.

[참조 : 당신은 당신이 터널을 사용하지 않도록 산란 스레드와 발신자 사이에 동기화를 추가 할 수 있습니다

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

socket.close 대 socket.shutdown 그것은 준비가되기 전에 . 예 :

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

데모 코드와 같이 서브 핸드러 해킹을하지 않아도됩니다. 댓글이 잘못되었습니다. 핸들러는 서버의 데이터에 액세스 할 수 있습니다. 핸들러 내에서 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, 좋은 캐치. [docs에 명시된] (http://docs.python.org/2/library/socketserver.html#requesthandler-objects) : "여러 인스턴스 속성을 사용할 수 있습니다 ... [서버 인스턴스를 포함하여] self.server , [RequestHandler]가 서버 별 정보에 액세스해야하는 경우에 사용됩니다. " – Air