2

我刚开始使用 Twisted。我想连接到 FTP 服务器并执行一些基本操作(如果可能,使用线程)。我正在使用这个例子

哪个做得很好。问题是如何在代码中添加 SOCKS4/5 代理使用?有人可以提供一个工作示例吗?我也试过这个链接

但,

    # Copyright (c) Twisted Matrix Laboratories.
    # See LICENSE for details.


    """
    An example of using the FTP client
    """

    # Twisted imports
    from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
    from twisted.internet.protocol import Protocol, ClientCreator
    from twisted.python import usage
    from twisted.internet import reactor, endpoints

    # Socks support test
    from socksclient import SOCKSv4ClientProtocol, SOCKSWrapper
    from twisted.web import client

    # Standard library imports
    import string
    import sys
    try:
        from cStringIO import StringIO
    except ImportError:
        from StringIO import StringIO


    class BufferingProtocol(Protocol):
        """Simple utility class that holds all data written to it in a buffer."""
        def __init__(self):
            self.buffer = StringIO()

        def dataReceived(self, data):
            self.buffer.write(data)

    # Define some callbacks

    def success(response):
        print 'Success!  Got response:'
        print '---'
        if response is None:
            print None
        else:
            print string.join(response, '\n')
        print '---'


    def fail(error):
        print 'Failed.  Error was:'
        print error

    def showFiles(result, fileListProtocol):
        print 'Processed file listing:'
        for file in fileListProtocol.files:
            print '    %s: %d bytes, %s' \
                  % (file['filename'], file['size'], file['date'])
        print 'Total: %d files' % (len(fileListProtocol.files))

    def showBuffer(result, bufferProtocol):
        print 'Got data:'
        print bufferProtocol.buffer.getvalue()


    class Options(usage.Options):
        optParameters = [['host', 'h', 'example.com'],
                         ['port', 'p', 21],
                         ['username', 'u', 'webmaster'],
                         ['password', None, 'justapass'],
                         ['passive', None, 0],
                         ['debug', 'd', 1],
                        ]

    # Socks support                    
    def wrappercb(proxy):
        print "connected to proxy", proxy
        pass

    def run():
        def sockswrapper(proxy, url):
            dest = client._parse(url) # scheme, host, port, path
            endpoint = endpoints.TCP4ClientEndpoint(reactor, dest[1], dest[2])
            return SOCKSWrapper(reactor, proxy[1], proxy[2], endpoint)

        # Get config
        config = Options()
        config.parseOptions()
        config.opts['port'] = int(config.opts['port'])
        config.opts['passive'] = int(config.opts['passive'])
        config.opts['debug'] = int(config.opts['debug'])

        # Create the client
        FTPClient.debug = config.opts['debug']
        creator = ClientCreator(reactor, FTPClient, config.opts['username'],
                                config.opts['password'], passive=config.opts['passive'])
        #creator.connectTCP(config.opts['host'], config.opts['port']).addCallback(connectionMade).addErrback(connectionFailed)

        # Socks support
        proxy = (None, '1.1.1.1', 1111, True, None, None)
        sw = sockswrapper(proxy, "ftp://example.com")
        d = sw.connect(creator)
        d.addCallback(wrappercb)

        reactor.run()

    def connectionFailed(f):
        print "Connection Failed:", f
        reactor.stop()

    def connectionMade(ftpClient):
        # Get the current working directory
        ftpClient.pwd().addCallbacks(success, fail)

        # Get a detailed listing of the current directory
        fileList = FTPFileListProtocol()
        d = ftpClient.list('.', fileList)
        d.addCallbacks(showFiles, fail, callbackArgs=(fileList,))

        # Change to the parent directory
        ftpClient.cdup().addCallbacks(success, fail)

        # Create a buffer
        proto = BufferingProtocol()

        # Get short listing of current directory, and quit when done
        d = ftpClient.nlst('.', proto)
        d.addCallbacks(showBuffer, fail, callbackArgs=(proto,))
        d.addCallback(lambda result: reactor.stop())


    # this only runs if the module was *not* imported
    if __name__ == '__main__':
        run()

我知道代码是错误的。我需要解决方案。

4

1 回答 1

2

好的,这里有一个解决方案(gist),它使用了 python 的内置ftplib以及开源SocksiPy模块

它不使用twisted,也没有明确使用线程,但是使用和在python的标准模块中很容易在线程threading.Thread之间threading.Queue使用threading通信

基本上,我们需要继承 ftplib.FTP 以支持替换我们自己的create_connection方法并添加代理配置语义。

“主要”逻辑只是配置一个通过 localhost socks 代理连接的 FTP 客户端,例如由 创建的代理,ssh -D localhost:1080 socksproxy.example.com并将 GNU autoconf 的源快照下载到本地磁盘。

import ftplib
import socket
import socks  # socksipy (https://github.com/mikedougherty/SocksiPy)


class FTP(ftplib.FTP):
    def __init__(self, host='', user='', passwd='', acct='',
                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
                 proxyconfig=None):
        """Like ftplib.FTP constructor, but with an added `proxyconfig` kwarg

        `proxyconfig` should be a dictionary that may contain the following
        keys:

        proxytype - The type of the proxy to be used. Three types
                are supported: PROXY_TYPE_SOCKS4 (including socks4a),
                PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
        addr -      The address of the server (IP or DNS).
        port -      The port of the server. Defaults to 1080 for SOCKS
                servers and 8080 for HTTP proxy servers.
        rdns -      Should DNS queries be preformed on the remote side
                (rather than the local side). The default is True.
                Note: This has no effect with SOCKS4 servers.
        username -  Username to authenticate with to the server.
                The default is no authentication.
        password -  Password to authenticate with to the server.
                Only relevant when username is also provided.
        """
        self.proxyconfig = proxyconfig or {}
        ftplib.FTP.__init__(self, host, user, passwd, acct, timeout)

    def connect(self, host='', port=0, timeout=-999):
        '''Connect to host.  Arguments are:
         - host: hostname to connect to (string, default previous host)
         - port: port to connect to (integer, default previous port)
        '''
        if host != '':
            self.host = host
        if port > 0:
            self.port = port
        if timeout != -999:
            self.timeout = timeout
        self.sock = self.create_connection(self.host, self.port)
        self.af = self.sock.family
        self.file = self.sock.makefile('rb')
        self.welcome = self.getresp()
        return self.welcome

    def create_connection(self, host=None, port=None):
        host, port = host or self.host, port or self.port
        if self.proxyconfig:

            phost, pport = self.proxyconfig['addr'], self.proxyconfig['port']
            err = None
            for res in socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM):
                af, socktype, proto, canonname, sa = res
                sock = None
                try:
                    sock = socks.socksocket(af, socktype, proto)
                    sock.setproxy(**self.proxyconfig)

                    if self.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
                        sock.settimeout(self.timeout)
                    sock.connect((host, port))
                    return sock

                except socket.error as _:
                    err = _
                    if sock is not None:
                        sock.close()

            if err is not None:
                raise err
            else:
                raise socket.error("getaddrinfo returns an empty list")
        else:
            sock = socket.create_connection((host, port), self.timeout)
        return sock

    def ntransfercmd(self, cmd, rest=None):
        size = None
        if self.passiveserver:
            host, port = self.makepasv()
            conn = self.create_connection(host, port)
            try:
                if rest is not None:
                    self.sendcmd("REST %s" % rest)
                resp = self.sendcmd(cmd)
                # Some servers apparently send a 200 reply to
                # a LIST or STOR command, before the 150 reply
                # (and way before the 226 reply). This seems to
                # be in violation of the protocol (which only allows
                # 1xx or error messages for LIST), so we just discard
                # this response.
                if resp[0] == '2':
                    resp = self.getresp()
                if resp[0] != '1':
                    raise ftplib.error_reply, resp
            except:
                conn.close()
                raise
        else:
            raise Exception("Active transfers not supported")
        if resp[:3] == '150':
            # this is conditional in case we received a 125
            size = ftplib.parse150(resp)
        return conn, size


if __name__ == '__main__':
    ftp = FTP(host='ftp.gnu.org', user='anonymous', passwd='guest',
              proxyconfig=dict(proxytype=socks.PROXY_TYPE_SOCKS5, rdns=False,
                               addr='localhost', port=1080))
    with open('autoconf-2.69.tar.xz', mode='w') as f:
        ftp.retrbinary("RETR /gnu/autoconf/autoconf-2.69.tar.xz", f.write)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         

详细说明为什么我问了一些我原来的问题:

1)您需要支持主动转账还是 PASV 转账就足够了?

通过 socks 代理进行主动传输要困难得多,因为它们需要使用 PORT 命令。使用 PORT 命令,您的 ftp 客户端会告诉 FTP 服务器在特定端口(例如,在您的 PC 上)连接到,以便发送数据。这可能不适用于防火墙或 NAT/路由器后面的用户。如果您的 SOCKS 代理服务器不在防火墙后面,或者有公共 IP,则可以支持主动传输,但这很复杂:它需要您的 SOCKS 服务器(ssh -D 支持)和客户端库(socksipy 不支持) ) 支持远程端口绑定。它还需要应用程序中的适当挂钩(我的示例抛出异常 if passiveserver = False)来执行远程 BIND 而不是本地 BIND。

2)是否必须使用扭曲?

Twisted 很棒,但我不是最擅长的,而且我还没有找到真正出色的 SOCKS 客户端实现。理想情况下,会有一个库允许您定义和/或将代理链接在一起,返回一个实现IReactorTCP接口的对象,但我还没有找到类似的东西。

3) 你的 socks 代理是在 VIP 后面还是直接连接到 Internet 的单个主机?

这很重要,因为 PASV 传输安全的工作方式。在 PASV 传输中,客户端要求服务器提供连接端口以开始数据传输。当服务器接受该端口上的连接时,它应该验证客户端是从与请求传输的连接相同的源 IP连接的。如果您的 SOCKS 服务器位于 VIP 后面,则为 PASV 传输建立的连接的出站 IP 不太可能与主要通信连接的出站 IP 匹配。

于 2012-12-29T23:26:05.740 回答