Я работаю над сценарием Python, чтобы периодически запрашивать несколько удаленных баз данных через установленный ssh-туннель. Я довольно хорошо знаком с библиотекой paramiko, так что это был мой выбор маршрута. Я бы предпочел сохранить это в полной версии Python, чтобы я мог использовать paramiko для решения ключевых проблем, а также использовать Python для запуска, управления и выключения туннелей ssh.
Здесь было несколько связанных вопросов по этой теме, но большинство из них показались неполными в ответах. Мое решение, приведенное ниже, состоит из нескольких решений, которые я нашел до сих пор.
Теперь о проблеме: я могу довольно легко создать первый туннель (в отдельном потоке) и выполнить свою работу с БД / python, но при попытке закрыть туннель локальный хост не освободит локальный порт 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 (), я понимаю, что код не совсем корректен, но в большей степени это эксперимент с попыткой правильно отключить туннель и проверить мои результаты.
Когда я впервые вызываю создание объекта 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, и как правильно его закрыть из моего скрипта. Если я попытаюсь связать другое соединение с другим IP-адресом, используя один и тот же локальный порт, до полного выхода из python (time_wait не является проблемой), то я получу печально известный адрес bind err 48. Заранее спасибо:)