如何在 Paramiko 服务器中实现端口转发?

use*_*000 7 python ssh paramiko

当您运行 SSHssh user@host -L <local port>:<remote host>:<remote port>并尝试通过本地端口连接时,会发生“direct-tcpip”请求(通常称为端口转发)。

我正在尝试在自定义 SSH 服务器上实现 direct-tcpip,Paramikocheck_channel_direct_tcpip_requestServerInterface类中提供了,以检查是否应该允许“direct-tcpip”请求,可以按如下方式实现:

class Server(paramiko.ServerInterface):
    # ...
    def check_channel_direct_tcpip_request(self, chanid, origin, destination):
        return paramiko.OPEN_SUCCEEDED
Run Code Online (Sandbox Code Playgroud)

但是,当我使用上述 SSH 命令并通过本地端口连接时,没有任何反应,可能是因为我需要自己实现连接处理。

阅读文档,似乎频道只有在OPEN_SUCCEDED返回后才打开。

返回请求后如何处理 direct-tcpipOPEN_SUCCEEDED请求?

Han*_*nnu 1

您确实需要设置自己的连接处理程序。这是一个冗长的答案,用于解释我所采取的步骤 - 如果您的服务器代码已经可以工作,那么您将不需要其中的一些步骤。整个正在运行的服务器示例在这里:https://controlc.com/25439153

我使用这里的 Paramiko 示例服务器代码https://github.com/paramiko/paramiko/blob/master/demos/demo_server.py作为基础,并在其上植入了一些套接字代码。这没有任何错误处理、线程相关的细节或任何其他“适当”的内容,但它允许您使用端口转发器。

这也有很多你不需要的东西,因为我不想开始整理虚拟示例代码。对此表示歉意。

首先,我们需要转发器工具。这将创建一个线程来运行“隧道”转发器。这也回答了您在哪里获得频道的问题。您可以accept()从传输中获取它,但您需要在转发器线程中执行此操作。正如您在OP中所述,它尚未出现在函数中check_channel_direct_tcpip_request(),但最终将可供线程使用。

def tunnel(sock, chan, chunk_size=1024):
    while True:
        r, w, x = select.select([sock, chan], [], [])

        if sock in r:
            data = sock.recv(chunk_size)
            if len(data) == 0:
                break
            chan.send(data)

        if chan in r:
            data = chan.recv(chunk_size)
            if len(data) == 0:
                break
            sock.send(data)

    chan.close()
    sock.close()


class ForwardClient(threading.Thread):
    daemon = True
    # chanid = 0

    def __init__(self, address, transport, chanid):
        threading.Thread.__init__(self)

        self.socket = socket.create_connection(address)
        self.transport = transport
        self.chanid = chanid

    def run(self):
        while True:
            chan = self.transport.accept(10)
            if chan == None:
                continue

            print("Got new channel (id: %i).", chan.get_id())

            if chan.get_id() == self.chanid:
                break

        peer = self.socket.getpeername()
        try:
            tunnel(self.socket, chan)
        except:
            pass
Run Code Online (Sandbox Code Playgroud)

回到示例服务器代码。您的服务器类需要将传输作为参数,与示例代码不同:

class Server(paramiko.ServerInterface):
    # 'data' is the output of base64.b64encode(key)
    # (using the "user_rsa_key" files)
    data = (
    b"AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hp"
    b"fAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMC"
    b"KDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iT"
    b"UWT10hcuO4Ks8="
    )
    good_pub_key = paramiko.RSAKey(data=decodebytes(data))

    def __init__(self, transport):
        self.transport = transport
        self.event = threading.Event()
Run Code Online (Sandbox Code Playgroud)

然后您将重写相关方法并在那里创建转发器:

def check_channel_direct_tcpip_request(self, chanid, origin, destination):
    print(chanid, origin, destination)
    f = ForwardClient(destination, self.transport, chanid)
    f.start()

    return paramiko.OPEN_SUCCEEDED
Run Code Online (Sandbox Code Playgroud)

您需要在创建服务器类时添加传输参数:

t.add_server_key(host_key)
server = Server(t)
Run Code Online (Sandbox Code Playgroud)

此示例服务器要求您在名为 test_rsa.key 的目录中拥有 RSA 私钥。在那里创建任何 RSA 密钥,您不需要它,但我没有费心从代码中删除它的使用。

然后您可以运行您的服务器(在端口 2200 上运行)并发出

ssh -p 2200 -L 2300:www.google.com:80  robey@localhost
Run Code Online (Sandbox Code Playgroud)

(密码是foo)

现在当你尝试时

telnet localhost 2300
Run Code Online (Sandbox Code Playgroud)

并在那里输入一些内容,你会得到谷歌的回应。