class Socks5Server(StreamServer):

    HOSTCACHE = {}
    HOSTCACHETIME = 1800

    def __init__(self, *args, **kw):
        super(Socks5Server, self).__init__(*args, **kw)
        self.remote_pool = ConnectionPool(factory=TcpConnector,
                                          max_conn=None,
                                          max_size=600,
                                          max_lifetime=300,
                                          backend="gevent")

        def log_tcp_pool_size(s):
            log("ConnPool size: %d, alive: %d" %
                (self.remote_pool.size(), self.remote_pool.alive()))
            spawn_later(10, s, s)

        def log_dns_pool_size(s):
            log("DNSPool size: %d" % len(self.HOSTCACHE))
            spawn_later(10, s, s)

        spawn_later(10, log_tcp_pool_size, log_tcp_pool_size)
        spawn_later(10, log_dns_pool_size, log_dns_pool_size)

    def close(self):
        self.remote_pool.release_all()
        super(Socks5Server, self).close()

    def handle(self, sock, address):
        rfile = sock.makefile('rb', -1)
        try:
            log('socks connection from ' + str(address))

            # 1. Version
            sock.recv(262)
            sock.send(b"\x05\x00")

            # 2. Request
            data = rfile.read(4)
            mode = ord(data[1])
            addrtype = ord(data[3])

            if addrtype == 1:  # IPv4
                addr = socket.inet_ntoa(rfile.read(4))
            elif addrtype == 3:  # Domain name
                domain = rfile.read(ord(sock.recv(1)[0]))
                addr = self.handle_dns(domain)

            port = struct.unpack('>H', rfile.read(2))

            if mode == 1:  # 1. Tcp connect
                try:
                    remote = self.remote_pool.get(host=addr, port=port[0])
                    reply = b"\x05\x00\x00\x01" + socket.inet_aton(addr) + \
                                struct.pack(">H", port[0])
                    sock.send(reply)
                    log('Begin data, %s:%s' % (addr, port[0]))
                    # 3. Transfering
                    l1 = spawn(self.handle_tcp, sock, remote)
                    l2 = spawn(self.handle_tcp, remote, sock)
                    gevent.joinall((l1, l2))
                    self.remote_pool.release_connection(remote)

                except socket.error:
                    log('Conn refused, %s:%s' % (addr, port[0]))
                    # Connection refused
                    reply = b'\x05\x05\x00\x01\x00\x00\x00\x00\x00\x00'
                    sock.send(reply)
                    raise

            else:
                reply = b"\x05\x07\x00\x01"  # Command not supported
                sock.send(reply)

        except socket.error:
            pass
        finally:
            log("Close handle")
            rfile.close()
            sock._sock.close()
            sock.close()

    def handle_dns(self, domain):

        if domain not in self.HOSTCACHE:
            log('Resolving ' + domain)
            addr = gethostbyname(domain)
            self.HOSTCACHE[domain] = addr
            spawn_later(self.HOSTCACHETIME,
                        lambda a: self.HOSTCACHE.pop(a, None), domain)
        else:
            addr = self.HOSTCACHE[domain]
            log('Hit resolv %s -> %s in cache' % (domain, addr))

        return addr

    def handle_tcp(self, fr, to):
        try:
            while to.send(fr.recv(4096)) > 0:
                continue
        except socket.error:
            pass
class Socks5Server(StreamServer):
    HOSTCACHE = {}
    HOSTCACHETIME = 1800

    def __init__(self, *args, **kw):
        super(Socks5Server, self).__init__(*args, **kw)
        self.remote_pool = ConnectionPool(factory=TcpConnector,
                                          max_size=600,
                                          max_lifetime=3,
                                          backend="gevent")

        def log_tcp_pool_size(s):
            log("ConnPool size: %d" % self.remote_pool.size)
            spawn_later(10, s, s)

        def log_dns_pool_size(s):
            log("DNSPool size: %d" % len(self.HOSTCACHE))
            spawn_later(10, s, s)

        spawn_later(10, log_tcp_pool_size, log_tcp_pool_size)
        spawn_later(10, log_dns_pool_size, log_dns_pool_size)

    def close(self):
        self.remote_pool.release_all()
        super(Socks5Server, self).close()

    def handle(self, sock, address):
        sock_file = sock.makefile('rb', -1)
        remote = None
        try:
            log('socks connection from ' + str(address))
            # 1. 设置handle超时
            sock.settimeout(10)
            sock.recv(262)
            sock.send(b"\x05\x00")
            # 2. 收到请求
            data = sock_file.read(4)
            decode_data = data.decode(encoding='ascii')
            mode = ord(decode_data[1])
            address_type = ord(decode_data[3])
            remote_address, port = self.get_remote_address_port(
                address_type, sock_file)
            if mode == 1:  # 1. 创建tcp连接
                self.create_remote_connection_pipe(remote_address, port, sock)
            else:
                reply = b"\x05\x07\x00\x01"  # 不支持的命令
                sock.send(reply)
                raise socket.error
        except socket.error:
            pass
        finally:
            if remote is not None:
                self.remote_pool.release_connection(remote)
            log("Close handle")
            sock_file.close()
            sock.close()

    def create_remote_connection_pipe(self, address, port, sock):
        """
        :param address: 远程地址
        :param port: 远程端口
        :param sock: 本地连接的sock
        :return:
        """
        remote = None
        try:
            remote = self.remote_pool.get(host=address, port=port)
            if self.remote_pool.too_old(remote):
                self.remote_pool.release_connection(remote)
                remote = self.remote_pool.get(host=address, port=port)
            reply = b"\x05\x00\x00\x01" + socket.inet_aton(address) + \
                    struct.pack(">H", port)
            sock.send(reply)
            log('Begin data, %s:%s' % (address, port))
            # 3.  协程监听sock读写
            l1 = spawn(handle_tcp, sock, remote)
            l2 = spawn(handle_tcp, remote, sock)
            gevent.joinall((l1, l2))
        except socket.error as error:
            log('Conn refused, %s:%s' % (address, port))
            # Connection refused
            reply = b'\x05\x05\x00\x01\x00\x00\x00\x00\x00\x00'
            sock.send(reply)
            raise error
        finally:
            if remote is not None:
                self.remote_pool.release_connection(remote)

    def get_remote_address_port(self, address_type, sock_file):
        address = None
        if address_type == AddressType.IPV4.value:  # ipv4地址
            address = socket.inet_ntoa(sock_file.read(4))
        elif address_type == AddressType.URL.value:  # 域名
            domain_length = ord(decode(sock_file.read(1)))
            domain = decode(sock_file.read(domain_length))
            address = self.handle_dns(domain)
        if address is None:
            raise ValueError('地址错误')
        port = struct.unpack('>H', sock_file.read(2))[0]
        return address, port

    def handle_dns(self, domain):

        if domain not in self.HOSTCACHE:
            log('Resolving ' + domain)
            addr = gethostbyname(domain)
            self.HOSTCACHE[domain] = addr
            spawn_later(self.HOSTCACHETIME,
                        lambda a: self.HOSTCACHE.pop(a, None), domain)
        else:
            addr = self.HOSTCACHE[domain]
            log('Hit resolv %s -> %s in cache' % (domain, addr))

        return addr