Problema de apagado del túnel Paramiko SSH
Estoy trabajando en un script de Python para consultar algunas bases de datos remotas a través de un túnel ssh establecido de vez en cuando. Estoy bastante familiarizado con la biblioteca paramiko, así que esa fue mi elección de ruta. Prefiero mantener esto en python completo para poder usar paramiko para tratar problemas clave, así como también usar python para iniciar, controlar y apagar los túneles ssh.
Ha habido algunas preguntas relacionadas por aquí sobre este tema, pero la mayoría de ellas parecían incompletas en las respuestas. Mi solución a continuación es un hackeado de las soluciones que he encontrado hasta ahora.
Ahora para el problema: soy capaz de crear el primer túnel con bastante facilidad (en un hilo separado) y hacer mis cosas de DB / python, pero cuando intento cerrar el túnel, el host local no liberará el puerto local al que me uní. A continuación, he incluido mi fuente y los datos relevantes de netstat en cada paso del proceso.
#!/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()
Aunque terminaré usando un método de tipo stopTunnel (), me doy cuenta de que el código no es del todo correcto, sino más bien una experimentación de tratar de cerrar el túnel correctamente y probar mis resultados.
Cuando llamo por primera vez, creo el objeto DBTunnel y llamo startTunnel (), netstat produce lo siguiente:
tcp4 0 0 *.3333 *.* LISTEN
tcp4 0 0 MYIP.36316 REMOTE_HOST.22 ESTABLISHED
tcp4 0 0 127.0.0.1.5432 *.* LISTEN
Una vez que llamo a stopTunnel (), o incluso elimino el objeto DBTunnel en sí ... Me quedo con esta conexión hasta que salga de Python todos juntos, y lo que supongo que es el recolector de basura se encarga de ello:
tcp4 0 0 *.3333 *.* LISTEN
Sería bueno saber por qué este socket abierto se cuelga independientemente del objeto DBConnect y cómo cerrarlo correctamente desde mi script. Si intento vincular una conexión diferente a una IP diferente usando el mismo puerto local antes de salir completamente de python (time_wait no es el problema), entonces obtengo la infame dirección de error err 48 en uso. Gracias por adelantado :)