Ticket #9221: 0001-Use-SOCKS5-instead-of-SOCKS4-a.patch

File 0001-Use-SOCKS5-instead-of-SOCKS4-a.patch, 22.0 KB (added by yawning, 6 years ago)
  • ChangeLog

    From e3250055b960a1129d5cd0e226a1f1648a03e3da Mon Sep 17 00:00:00 2001
    From: Yawning Angel <yawning@schwanenlied.me>
    Date: Mon, 3 Mar 2014 20:53:48 +0000
    Subject: [PATCH 1/1] Use SOCKS5 instead of SOCKS4(a)
    
    A fairly straightforward mostly RFC 1928/RFC 1929 compliant SOCKS5 server, with
    the following caveats:
    
     * Like most other SOCKS5 implementations, GSSAPI is not supported.
     * Only the CONNECT command is supported.
     * The DOMAINNAME address type is not supported (intentionally).
     * When doing RFC 1929 Username/Password authentication, it is assumed that the
       pt-spec "use it to pass arguments" behavior is desired instead of actual
       authentication.
     * IPv6 is untested.
    
    Behavior is identical to the previous SOCKS4 code except that SOCKS5 is used.
    This means:
    
     * The SOCKS5 response is sent with a status code indication success immediately
       after the TCP/IP connection to the peer is established.  This should be
       changed eventually to be after the transport handshakes.
    
       See: SOCKSv5Protocol.send_reply()
    ---
     ChangeLog                             |   1 +
     obfsproxy/managed/client.py           |   2 +-
     obfsproxy/network/launch_transport.py |   2 +-
     obfsproxy/network/socks.py            | 456 ++++++++++++++++++++++++++--------
     4 files changed, 358 insertions(+), 103 deletions(-)
    
    diff --git a/ChangeLog b/ChangeLog
    index 8fb2b21..a3dab45 100644
    a b Changes in version 0.2.7 - UNRELEASED 
    33   exchange outside of the main event loop.  Patch by Yawning Angel.
    44 - Support gmpy2 if it is available in addition to gmpy.
    55   Patch by Yawning Angel.
     6 - Support SOCKS5 instead of SOCKS4.  Patch by Yawning Angel.
    67
    78
    89Changes in version 0.2.6 - 2014-02-03
  • obfsproxy/managed/client.py

    diff --git a/obfsproxy/managed/client.py b/obfsproxy/managed/client.py
    index 8169c40..bce5463 100644
    a b def do_managed_client(): 
    5656
    5757        should_start_event_loop = True
    5858        log.debug("Successfully launched '%s' at '%s'" % (transport, log.safe_addr_str(str(addrport))))
    59         ptclient.reportMethodSuccess(transport, "socks4", addrport, None, None)
     59        ptclient.reportMethodSuccess(transport, "socks5", addrport, None, None)
    6060
    6161    ptclient.reportMethodsEnd()
    6262
  • obfsproxy/network/launch_transport.py

    diff --git a/obfsproxy/network/launch_transport.py b/obfsproxy/network/launch_transport.py
    index 8ffa6b4..b9d7e9b 100644
    a b def launch_transport_listener(transport, bindaddr, role, remote_addrport, pt_con 
    3535    listen_port = int(bindaddr[1]) if bindaddr else 0
    3636
    3737    if role == 'socks':
    38         factory = socks.SOCKSv4Factory(transport_class, pt_config)
     38        factory = socks.SOCKSv5Factory(transport_class, pt_config)
    3939    elif role == 'ext_server':
    4040        assert(remote_addrport and ext_or_cookie_file)
    4141        factory = extended_orport.ExtORPortServerFactory(remote_addrport, ext_or_cookie_file, transport, transport_class, pt_config)
  • obfsproxy/network/socks.py

    diff --git a/obfsproxy/network/socks.py b/obfsproxy/network/socks.py
    index c7e8f47..bac7d9c 100644
    a b  
    1 import csv
    2 
    3 from twisted.protocols import socks
    4 from twisted.internet.protocol import Factory
     1from twisted.internet import reactor, protocol, error
    52
    63import obfsproxy.common.log as logging
    74import obfsproxy.network.network as network
    85import obfsproxy.transports.base as base
    96
     7import csv
     8import socket
     9import struct
     10
    1011log = logging.get_obfslogger()
    1112
    12 def split_socks_args(args_str):
     13"""
     14SOCKS5 Server:
     15
     16This is a SOCKS5 server written specifically for Tor Pluggable Transports.  It
     17is compliant with RFC 1928 and RFC 1929 with the following exceptions.
     18
     19 * GSSAPI Authentication is not and will not ever be supported.
     20 * The SOCKS5 CMDs BIND and UDP ASSOCIATE are not supported.
     21 * The SOCKS5 ATYP DOMAINNAME is not supported.  This is a intentional design
     22   choice.  While it would be trivial to support this, Pluggable Transports
     23   hitting up DNS is a DNS leak, and it is Tor's problem to pass acceptable
     24   addresses.
     25"""
     26
     27#
     28# SOCKS Server States
     29#
     30_SOCKS_ST_READ_METHODS = 1
     31_SOCKS_ST_AUTHENTICATING = 2
     32_SOCKS_ST_READ_REQUEST = 3
     33_SOCKS_ST_CONNECTING = 4
     34_SOCKS_ST_ESTABLISHED = 5
     35
     36#
     37# SOCKS5 Constants
     38#
     39_SOCKS_VERSION = 0x05
     40_SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED = 0x00
     41#_SOCKS_AUTH_GSSAPI = 0x01
     42_SOCKS_AUTH_USERNAME_PASSWORD = 0x02
     43_SOCKS_AUTH_NO_ACCEPTABLE_METHODS = 0xFF
     44_SOCKS_CMD_CONNECT = 0x01
     45#_SOCKS_CMD_BIND = 0x02
     46#_SOCKS_CMD_UDP_ASSOCIATE = 0x03
     47_SOCKS_ATYP_IP_V4 = 0x01
     48_SOCKS_ATYP_DOMAINNAME = 0x03
     49_SOCKS_ATYP_IP_V6 = 0x04
     50_SOCKS_RSV = 0x00
     51_SOCKS_RFC1929_VER = 0x01
     52_SOCKS_RFC1929_SUCCESS = 0x00
     53_SOCKS_RFC1929_FAIL = 0x01
     54
     55class SOCKSv5Reply():
     56    """
     57    SOCKS reply codes
     58    """
     59
     60    __slots__ = ['Succeded', 'GeneralFailure', 'ConnectionNotAllowed',
     61                 'NetworkUnreachable', 'HostUnreachable', 'ConnectionRefused',
     62                 'TTLExpired', 'CommandNotSupported', 'AddressTypeNotSupported']
     63
     64    Succeeded = 0x00
     65    GeneralFailure = 0x01
     66    ConnectionNotAllowed = 0x02
     67    NetworkUnreachable = 0x03
     68    HostUnreachable = 0x04
     69    ConnectionRefused = 0x05
     70    TTLExpired = 0x06
     71    CommandNotSupported = 0x07
     72    AddressTypeNotSupported = 0x08
     73
     74def _split_socks_args(args_str):
    1375    """
    1476    Given a string containing the SOCKS arguments (delimited by
    1577    semicolons, and with semicolons and backslashes escaped), parse it
    def split_socks_args(args_str): 
    1779    """
    1880    return csv.reader([args_str], delimiter=';', escapechar='\\').next()
    1981
    20 class MySOCKSv4Outgoing(socks.SOCKSv4Outgoing, network.GenericProtocol):
     82class SOCKSv5Outgoing(network.GenericProtocol):
    2183    """
    22     Represents a downstream connection from the SOCKS server to the
    23     destination.
    24 
    25     It monkey-patches socks.SOCKSv4Outgoing, because we need to pass
    26     our data to the pluggable transport before proxying them
    27     (Twisted's socks module did not support that).
     84    Represents a downstream connection from our SOCKS server to a remote peer.
    2885
    2986    Attributes:
    30     circuit: The circuit this connection belongs to.
    31     buffer: Buffer that holds data that can't be proxied right
    32             away. This can happen because the circuit is not yet
    33             complete, or because the pluggable transport needs more
    34             data before deciding what to do.
     87    circuit: The curcuit object this connection belongs to.
     88    buffer: Buffer that holds data waiting to be processed.
    3589    """
    3690
    37     def __init__(self, socksProtocol):
    38         """
    39         Constructor.
     91    name = None
    4092
    41         'socksProtocol' is a 'SOCKSv4Protocol' object.
    42         """
     93    _socks = None
     94
     95    def __init__(self, socks):
    4396        self.name = "socks_down_%s" % hex(id(self))
    44         self.socksProtocol = socksProtocol
     97        self._socks = socks
    4598
    46         network.GenericProtocol.__init__(self, socksProtocol.circuit)
    47         return super(MySOCKSv4Outgoing, self).__init__(socksProtocol)
     99        network.GenericProtocol.__init__(self, socks.circuit)
     100
     101    def connectionMade(self):
     102        self._socks._other_conn = self
     103        self._socks.setup_circuit()
     104        # XXX: The transport should do this after handshaking
     105        self._socks.send_reply(SOCKSv5Reply.Succeeded)
    48106
    49107    def dataReceived(self, data):
    50108        log.debug("%s: Received %d bytes." % (self.name, len(data)))
    51109
    52         # If the circuit was not set up, set it up now.
    53         if not self.circuit.circuitIsReady():
    54             self.socksProtocol.set_up_circuit()
    55 
     110        assert self.circuit.circuitIsReady()
    56111        self.buffer.write(data)
    57112        self.circuit.dataReceived(self.buffer, self)
    58113
    59     def close(self): # XXX code duplication
    60         """
    61         Close the connection.
    62         """
    63         if self.closed:
    64             return # NOP if already closed
    65 
    66         log.debug("%s: Closing connection." % self.name)
    67 
    68         self.transport.loseConnection()
    69         self.closed = True
    70 
    71     def connectionLost(self, reason):
    72         network.GenericProtocol.connectionLost(self, reason)
    73 
    74 # Monkey patches socks.SOCKSv4Outgoing with our own class.
    75 socks.SOCKSv4Outgoing = MySOCKSv4Outgoing
    76 
    77 class SOCKSv4Protocol(socks.SOCKSv4, network.GenericProtocol):
     114class SOCKSv5Protocol(network.GenericProtocol):
    78115    """
    79     Represents an upstream connection from a SOCKS client to our SOCKS
    80     server.
     116    Represents an upstream connection from a SOCKS client to our SOCKS server.
    81117
    82     It overrides socks.SOCKSv4 because py-obfsproxy's connections need
    83     to have a circuit and obfuscate traffic before proxying it.
     118    Attributes:
     119    circuit: The curcuit object this connection belongs to.
     120    buffer: Buffer that holds data waiting to be processed.
    84121    """
    85122
     123    name = None
     124
     125    _state = None
     126    _auth_method = None
     127    _other_conn = None
     128
    86129    def __init__(self, circuit):
    87130        self.name = "socks_up_%s" % hex(id(self))
     131        self._state = _SOCKS_ST_READ_METHODS
    88132
    89133        network.GenericProtocol.__init__(self, circuit)
    90         socks.SOCKSv4.__init__(self)
    91134
    92     def dataReceived(self, data):
    93         """
    94         Received some 'data'. They might be SOCKS handshake data, or
    95         actual upstream traffic. Figure out what it is and either
    96         complete the SOCKS handshake or proxy the traffic.
    97         """
    98 
    99         # SOCKS handshake not completed yet: let the overriden socks
    100         # module complete the handshake.
    101         if not self.otherConn:
    102             log.debug("%s: Received SOCKS handshake data." % self.name)
    103             return socks.SOCKSv4.dataReceived(self, data)
     135    def connectionLost(self, reason):
     136        network.GenericProtocol.connectionLost(self, reason)
    104137
     138    def dataReceived(self, data):
    105139        log.debug("%s: Received %d bytes." % (self.name, len(data)))
    106140        self.buffer.write(data)
    107141
     142        if self._state == _SOCKS_ST_READ_METHODS:
     143            self._process_method_select()
     144        elif self._state == _SOCKS_ST_AUTHENTICATING:
     145            self._process_authentication()
     146        elif self._state == _SOCKS_ST_READ_REQUEST:
     147            self._process_request()
     148        elif self._state == _SOCKS_ST_CONNECTING:
     149            log.warning("%s: Client sent data before receiving response" % self.name)
     150            self.transport.loseConnection()
     151        elif self._state == _SOCKS_ST_ESTABLISHED:
     152            assert self.circuit.circuitIsReady()
     153            self.circuit.dataReceived(self.buffer, self)
     154        else:
     155            log.warning("%s: Invalid state in SOCKS5 Server: '%d'" % (self.name, self._state))
     156            self.transport.loseConnection()
     157
     158    def _process_method_select(self):
    108159        """
    109         If we came here with an incomplete circuit, it means that we
    110         finished the SOCKS handshake and connected downstream. Set up
    111         our circuit and start proxying traffic.
     160        Parse Version Identifier/Method Selection Message, and send a response
    112161        """
    113         if not self.circuit.circuitIsReady():
    114             self.set_up_circuit()
    115 
    116         self.circuit.dataReceived(self.buffer, self)
    117162
    118     def set_up_circuit(self):
     163        msg = self.buffer.peek()
     164        if len(msg) < 2:
     165            return
     166        ver, nmethods = struct.unpack("BB", msg[0:2])
     167        if ver != _SOCKS_VERSION:
     168            log.warning("%s: Invalid SOCKS version: '%d'" % (self.name, ver))
     169            self.transport.loseConnection()
     170            return
     171        if nmethods == 0:
     172            log.warning("%s: No Authentication method present" % self.name)
     173            self.transport.loseConnection()
     174            return
     175
     176        #
     177        # Ensure that the entire message is present, and find the "best"
     178        # authentication method.
     179        #
     180        # In a perfect world, the transport will specify that authentication
     181        # is required, but since tor will do the work for us, this will
     182        # suffice.
     183        #
     184
     185        if len(msg) < 2 + nmethods:
     186            return
     187        methods = msg[2:2 + nmethods]
     188        if chr(_SOCKS_AUTH_USERNAME_PASSWORD) in methods:
     189            log.debug("%s: Username/Password Authentication" % self.name)
     190            self._auth_method = _SOCKS_AUTH_USERNAME_PASSWORD
     191        elif chr(_SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED) in methods:
     192            log.debug("%s: No Authentication Required" % self.name)
     193            self._auth_method = _SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED
     194        else:
     195            log.warning("%s: No Compatible Authentication method" % self.name)
     196            self._auth_method = _SOCKS_AUTH_NO_ACCEPTABLE_METHODS
     197
     198        # Ensure there is no trailing garbage
     199        self.buffer.drain(2 + nmethods)
     200        if len(self.buffer) > 0:
     201            log.warning("%s: Peer sent trailing garbage after method select" % self.name)
     202            self.transport.loseConnection()
     203            return
     204
     205        # Send Method Selection Message
     206        self.transport.write(struct.pack("BB", _SOCKS_VERSION, self._auth_method))
     207        if self._auth_method == _SOCKS_AUTH_NO_ACCEPTABLE_METHODS:
     208            self.transport.loseConnection()
     209            return
     210
     211        self._state = _SOCKS_ST_AUTHENTICATING
     212
     213    def _process_authentication(self):
    119214        """
    120         Set the upstream/downstream SOCKS connections on the circuit.
     215        Handle client data when authenticating
    121216        """
    122217
    123         assert(self.otherConn)
    124         self.circuit.setDownstreamConnection(self.otherConn)
    125         self.circuit.setUpstreamConnection(self)
    126 
    127     def authorize(self, code, server, port, user):
     218        if self._auth_method == _SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED:
     219            self._state = _SOCKS_ST_READ_REQUEST
     220            self._process_request()
     221        elif self._auth_method == _SOCKS_AUTH_USERNAME_PASSWORD:
     222            self._process_rfc1929_request()
     223        else:
     224            log.warning("%s: Peer sent data when we failed to negotiate auth" % (self.name))
     225            self.buffer.drain()
     226            self.transport.loseConnection()
     227
     228    def _process_rfc1929_request(self):
    128229        """
    129         (Overriden)
    130         Accept or reject a SOCKS client that wants to connect to
    131         'server':'port', with the SOCKS4 username 'user'.
     230        Handle RFC1929 Username/Password authentication requests
    132231        """
    133232
    134         if not user: # No SOCKS arguments were specified.
    135             return True
    136 
    137         # If the client sent us SOCKS arguments, we must parse them
    138         # and send them to the appropriate transport.
    139         log.debug("Got '%s' as SOCKS arguments." % user)
    140 
     233        msg = self.buffer.peek()
     234        if len(msg) < 2:
     235            return
     236
     237        # Parse VER, ULEN
     238        ver, ulen = struct.unpack("BB", msg[0:2])
     239        if ver != _SOCKS_RFC1929_VER:
     240            log.warning("%s: Invalid RFC1929 version: '%d'" % (self.name, ver))
     241            self._send_rfc1929_reply(False)
     242            return
     243        if ulen == 0:
     244            log.warning("%s: Username length is 0" % self.name)
     245            self._send_rfc1929_reply(False)
     246            return
     247        if len(msg) < 2 + ulen:
     248            return
     249        uname = msg[2:2 + ulen]
     250        if len(msg) < 2 + ulen + 1:
     251            return
     252        plen = struct.unpack("B", msg[2 + ulen])[0]
     253        if len(msg) < 2 + ulen + 1 + plen:
     254            return
     255        if plen == 0:
     256            log.warning("%s: Password length is 0" % self.name)
     257            self._send_rfc1929_reply(False)
     258            return
     259        passwd = msg[2 + ulen + 1:2 + ulen + 1 + plen]
     260
     261        #
     262        # Begin the pt-spec specific SOCKS auth braindamage:
     263        #
     264
     265        args = uname
     266        if plen > 1 or ord(passwd[0]) != 0:
     267            args += passwd
    141268        try:
    142             socks_args = split_socks_args(user)
    143         except csv.Error, err:
     269            split_args = _split_socks_args(args)
     270        except csvError, err:
    144271            log.warning("split_socks_args failed (%s)" % str(err))
    145             return False
    146 
     272            self._send_rfc1929_reply(False)
     273            return
    147274        try:
    148             self.circuit.transport.handle_socks_args(socks_args)
     275            self.circuit.transport.handle_socks_args(split_args)
    149276        except base.SOCKSArgsError:
    150             return False # Transports should log the issue themselves
     277            # Transports should log the issue themselves
     278            self._send_rfc1929_reply(False)
     279            return
    151280
    152         return True
     281        # Ensure there is no trailing garbage
     282        self.buffer.drain(2 + ulen + 1 + plen)
     283        if len(self.buffer) > 0:
     284            log.warning("%s: Peer sent trailing garbage after RFC1929 auth" % self.name)
     285            self.transport.loseConnection()
     286            return
    153287
    154     def connectionLost(self, reason):
    155         network.GenericProtocol.connectionLost(self, reason)
     288        self._send_rfc1929_reply(True)
     289
     290    def _send_rfc1929_reply(self, success):
     291        """
     292        Send a RFC1929 Username/Password Authentication response
     293        """
     294
     295        if success == True:
     296            self.transport.write(struct.pack("BB", 1, _SOCKS_RFC1929_SUCCESS))
     297            self._state = _SOCKS_ST_READ_REQUEST
     298        else:
     299            self.transport.write(struct.pack("BB", 1, _SOCKS_RFC1929_FAIL))
     300            self.transport.loseConnection()
     301
     302    def _process_request(self):
     303        """
     304        Parse the client request, and setup the TCP/IP connection
     305        """
     306
     307        msg = self.buffer.peek()
     308        if len(msg) < 4:
     309            return
     310
     311        ver, cmd, rsv, atyp = struct.unpack("BBBB", msg[0:4])
     312        if ver != _SOCKS_VERSION:
     313            log.warning("%s: Invalid SOCKS version: '%d'" % (self.name, ver))
     314            self.send_reply(SOCKSv5Reply.GeneralFailure)
     315            return
     316        if cmd != _SOCKS_CMD_CONNECT:
     317            log.warning("%s: Invalid SOCKS command: '%d'" % (self.name, cmd))
     318            self.send_reply(SOCKSv5Reply.CommandNotSupported)
     319            return
     320        if rsv != _SOCKS_RSV:
     321            log.warning("%s: Invalid SOCKS RSV: '%d'" % (self.name, rsv))
     322            self.send_reply(SOCKSv5Reply.GeneralFailure)
     323            return
     324
     325        # Deal with the address
     326        addr = None
     327        if atyp == _SOCKS_ATYP_IP_V4:
     328            if len(msg) < 4 + 4 + 2:
     329                return
     330            addr = socket.inet_ntoa(msg[4:8])
     331            self.buffer.drain(4 + 4)
     332        elif atyp == _SOCKS_ATYP_IP_V6:
     333            if len(msg) < 4 + 16 + 2:
     334                return
     335            try:
     336                addr = socket.inet_ntop(socket.AF_INET6, msg[4:16])
     337            except:
     338                log.warning("%s: Failed to parse IPv6 address" % self.name)
     339                self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
     340                return
     341            self.buffer.drain(4 + 16)
     342        elif atyp == _SOCKS_ATYP_DOMAINNAME:
     343            log.warning("%s: Domain Name address type is not supported" % self.name)
     344            self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
     345            return
     346        else:
     347            log.warning("%s: Invalid SOCKS address type: '%d'" % (self.name, atyp))
     348            self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
     349            return
     350
     351        # Deal with the port
     352        port = struct.unpack("!H", self.buffer.read(2))[0]
     353
     354        # Ensure there is no trailing garbage
     355        if len(self.buffer) > 0:
     356            log.warning("%s: Peer sent trailing garbage after request" % self.name)
     357            self.send_reply(SOCKSv5Reply.GeneralFailure)
     358            return
     359
     360        # Connect -> addr/port
     361        d = protocol.ClientCreator(reactor, SOCKSv5Outgoing, self).connectTCP(addr, port)
     362        d.addErrback(self._handle_connect_failure)
     363
     364        self._state = _SOCKS_ST_CONNECTING
     365
     366    def _handle_connect_failure(self, failure):
     367        log.warning("%s: Failed to connect to peer: %s" % (self.name, failure))
     368
     369        # Map common twisted errors to SOCKS error codes
     370        if failure.type == error.NoRouteError:
     371            self.send_reply(SOCKSv5Reply.NetworkUnreachable)
     372        elif failure.type == error.ConnectionRefusedError:
     373            self.send_reply(SOCKSv5Reply.ConnectionRefused)
     374        elif failure.type == error.TCPTimedOutError or failure.type == error.TimeoutError:
     375            self.send_reply(SOCKSv5Reply.TTLExpired)
     376        elif failure.type == error.UnsupportedAddressFamily:
     377            self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
     378        else:
     379            self.send_reply(SOCKSv5Reply.GeneralFailure)
     380
     381    def setup_circuit(self):
     382        assert self._other_conn
     383        self.circuit.setDownstreamConnection(self._other_conn)
     384        self.circuit.setUpstreamConnection(self)
     385
     386    def send_reply(self, reply):
     387        """
     388        Send a reply to the request, and complete circuit setup
     389        """
    156390
    157 class SOCKSv4Factory(Factory):
     391        if reply == SOCKSv5Reply.Succeeded:
     392            host = self.transport.getHost()
     393            port = host.port
     394            try:
     395                raw_addr = socket.inet_aton(host.host)
     396                self.transport.write(struct.pack("BBBB", _SOCKS_VERSION, reply, _SOCKS_RSV, _SOCKS_ATYP_IP_V4) + raw_addr + struct.pack("!H",port))
     397            except socket.error:
     398                try:
     399                    raw_addr = socket.inet_pton(socket.AF_INET6, host.host)
     400                    self.transport.write(struct.pack("BBBB", _SOCKS_VERSION, reply, _SOCKS_RSV, _SOCKS_ATYP_IP_V6) + raw_addr + struct.pack("!H",port))
     401                except:
     402                    log.warning("%s: Failed to parse bound address" % self.name)
     403                    self.send_reply(SOCKSv5Reply.GeneralFailure)
     404                    return
     405
     406            self._state = _SOCKS_ST_ESTABLISHED
     407        else:
     408            self.transport.write(struct.pack("!BBBBIH", _SOCKS_VERSION, reply, _SOCKS_RSV, _SOCKS_ATYP_IP_V4, 0, 0))
     409            self.transport.loseConnection()
     410
     411class SOCKSv5Factory(protocol.Factory):
    158412    """
    159     A SOCKSv4 factory.
     413    A SOCKSv5 Factory.
    160414    """
    161415
    162416    def __init__(self, transport_class, pt_config):
    class SOCKSv4Factory(Factory): 
    174428
    175429        circuit = network.Circuit(self.transport_class())
    176430
    177         return SOCKSv4Protocol(circuit)
     431        return SOCKSv5Protocol(circuit)