Возможно, вы захотите добавить некоторую синхронизацию между порожденным потоком и вызывающей стороной, чтобы вы не пытались использовать туннель до его готовности. Что-то вроде:

отаю над сценарием Python, чтобы периодически запрашивать несколько удаленных баз данных через установленный ssh-туннель. Я довольно хорошо знаком с библиотекой paramiko, так что это был мой выбор маршрута. Я бы предпочел сохранить это в полном Python, чтобы я мог использовать paramiko для решения ключевых проблем, а также использовать Python для запуска, управления и выключения туннелей 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. У меня остается это соединение до тех пор, пока я все вместе не выйду из python, и об этом позаботится то, что я предполагаю как сборщик мусора:

tcp4       0      0 *.3333                 *.*                    LISTEN

Было бы неплохо выяснить, почему этот открытый сокет висит независимо от объекта DBConnect, и как правильно его закрыть из моего скрипта. Если я попытаюсь связать другое соединение с другим IP-адресом, используя один и тот же локальный порт, до полного выхода из python (time_wait не является проблемой), то я получу печально известный адрес bind err 48. Заранее спасибо :)

Ответы на вопрос(3)

Ваш ответ на вопрос