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

さて、ここにpython のビルトインとオープン ソースモジュールを使用するソリューション (要点) があります。ftplibSocksiPy

それはツイストを使用せず、スレッドを明示的に使用しませんが、スレッド間の使用と通信は、Python の標準モジュールで非常に簡単にthreading.Thread実行できますthreading.Queuethreading

基本的に、ftplib.FTP をサブクラス化して、独自のcreate_connectionメソッドの置換をサポートし、プロキシ構成セマンティクスを追加する必要があります。

「メイン」ロジックは、によって作成されたものなどの localhost ソックス プロキシ経由で接続する FTP クライアントを構成し、ssh -D localhost:1080 socksproxy.example.comGNU 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 転送で十分ですか?

アクティブな転送は、PORT コマンドを使用する必要があるため、socks プロキシ経由で行うのははるかに困難です。PORT コマンドを使用すると、FTP クライアントは、データを送信するために特定のポート (PC など) で接続するようFTP サーバーに指示します。これは、ファイアウォールまたは NAT/ルーターの背後にあるユーザーには機能しない可能性があります。SOCKS プロキシ サーバーがファイアウォールの背後にない場合、またはパブリック IP を持っている場合、アクティブな転送をサポートすることは可能ですが、複雑です: SOCKS サーバー (ssh -D はこれをサポートします) とクライアント ライブラリ (socksipy はサポートしません) が必要です。 ) リモート ポート バインドをサポートします。またpassiveserver = False、ローカルの BIND ではなくリモートの BIND を実行するには、アプリケーションに適切なフックが必要です (私の例では例外 if をスローします)。

2) ツイストを使用する必要がありますか?

Twisted は素晴らしいですが、私はそれが得意ではなく、本当に優れた SOCKS クライアントの実装を見つけていません。理想的には、 IReactorTCPインターフェースを実装するオブジェクトを返す、プロキシを定義および/またはチェーンできるライブラリがそこにあるはずですが、私はまだこのようなものをまだ見つけていません。

3) ソックス プロキシは VIP の背後にありますか、それともインターネットに直接接続されている単一のホストだけですか?

これは、PASV 転送セキュリティが機能する方法のために重要です。PASV 転送では、クライアントは、データ転送を開始するために接続するポートを提供するようサーバーに要求します。サーバーがそのポートで接続を受け入れる場合、転送を要求した接続と同じソース IP からクライアントが接続されていることを確認する必要があります。SOCKS サーバーが VIP の背後にある場合、PASV 転送用に確立された接続の発信 IP がプライマリ通信接続の発信 IP と一致する可能性は低くなります。

于 2012-12-29T23:26:05.740 に答える