Example #1
0
    def __init__(self, proxy, ):
        self.logger = logging.getLogger('ss')
        self.proxy = proxy
        ssmethod, sspassword = self.proxy.username, self.proxy.password
        if sspassword is None:
            ssmethod, sspassword = base64.b64decode(ssmethod).decode().split(':', 1)
        ssmethod = ssmethod.lower()

        self._address = None
        self._port = 0
        self.client_reader = None
        self.client_writer = None
        self.remote_reader = None
        self.remote_writer = None
        self.task = None

        self.aead = is_aead(ssmethod)
        self.crypto = Encryptor(sspassword, ssmethod)
        self.connected = False
        self.last_active = time.time()
        # if eof recieved
        self.remote_eof = False
        self.client_eof = False
        self.data_recved = False
        self._buf = b''
Example #2
0
    def __init__(self, server):
        self.server = server
        self.logger = server.logger
        self.user_mgr = self.server.user_mgr
        self.address = self.server.address

        self.encryptor = Encryptor(self.server.psk, self.server.method)
        self._buf = b''

        self.client_address = None
        self.client_reader = None
Example #3
0
 async def on_remote_recv(self, client_addr, remote_addr, dgram, data):
     '''
         create dgram, encrypt and send to client
     '''
     self.logger.debug('on_remote_recv %r, %r', remote_addr, client_addr)
     if data:
         buf = data
     else:
         remote_ip = ipaddress.ip_address(remote_addr[0])
         buf = b'\x01' if remote_ip.version == 4 else b'\x04'
         buf += remote_ip.packed
         buf += struct.pack(b'>H', remote_addr[1])
         buf += dgram
     cipher = Encryptor(self.__key, self.method)
     buf = cipher.encrypt_once(buf)
     await self.server_stream.send(buf, client_addr)
Example #4
0
def test_one(method):
    data = os.urandom(10240)
    if is_aead(method):
        cipher = AEncryptor(b'123456', method, b"ctx", check_iv=False)
        cipher1 = AEncryptor(b'123456', method, b"ctx", check_iv=False)
    else:
        cipher = Encryptor(b'123456', method, check_iv=False)
        cipher1 = Encryptor(b'123456', method, check_iv=False)
    ct1 = cipher.encrypt(data)
    cipher1.decrypt(ct1)
    time_log = time.time()
    for _ in range(1024):
        ct1 = cipher.encrypt(data)
        ct2 = cipher.encrypt(data)
        cipher1.decrypt(ct1)
        cipher1.decrypt(ct2)
    return time.time() - time_log
Example #5
0
    def decrypt_parse(self, data):
        cipher = Encryptor(self.__key, self.method)
        data = cipher.decrypt(data)

        data = io.BytesIO(data)
        addrtype = data.read(1)[0]
        if addrtype == 1:
            addr = data.read(4)
            addr = socket.inet_ntoa(addr)
        elif addrtype == 3:
            addr = data.read(1)
            addr = data.read(addr[0])
            addr = addr.decode('ascii')
        else:
            addr = data.read(16)
            addr = socket.inet_ntop(socket.AF_INET6, addr)
        port = data.read(2)
        port, = struct.unpack('>H', port)

        dgram = data.read()
        return (addr, port), dgram, data
Example #6
0
 def get_cipher(self):
     cipher = Encryptor(self.sspassword, self.ssmethod)
     return cipher
Example #7
0
    async def get_key(self):
        self.logger.debug('hxsocks2 getKey')
        usn, psw = (self.proxy.username, self.proxy.password)
        self.logger.info('%s connect to server', self.name)
        from .connection import open_connection
        self.remote_reader, self.remote_writer, _ = await open_connection(
            self.proxy.hostname,
            self.proxy.port,
            proxy=self.proxy.get_via(),
            timeout=self.timeout,
            tunnel=True)

        # prep key exchange request
        self.__pskcipher = Encryptor(self._psk, self.method)
        ecc = ECC(self.__pskcipher._key_len)
        pubk = ecc.get_pub_key()
        ts = int(time.time()) // 30
        ts = struct.pack('>I', ts)
        padding_len = random.randint(64, 255)
        data = b''.join([chr(len(pubk)).encode('latin1'),
                         pubk,
                         hmac.new(psw.encode(), ts + pubk + usn.encode(), hashlib.sha256).digest(),
                         b'\x00' * padding_len])
        data = chr(20).encode() + struct.pack('>H', len(data)) + data

        ct = self.__pskcipher.encrypt(data)

        # send key exchange request
        self.remote_writer.write(ct)
        await self.remote_writer.drain()

        # read iv
        iv = await self._rfile_read(self.__pskcipher._iv_len, self.timeout)
        self.__pskcipher.decrypt(iv)

        # read server response
        if is_aead(self.method):
            ct_len = await self._rfile_read(18, self.timeout)
            ct_len = self.__pskcipher.decrypt(ct_len)
            ct_len, = struct.unpack('!H', ct_len)
            ct = await self._rfile_read(ct_len + 16)
            ct = self.__pskcipher.decrypt(ct)
            data = ct[2:]
        else:
            resp_len = await self._rfile_read(2, self.timeout)
            resp_len = self.__pskcipher.decrypt(resp_len)
            resp_len, = struct.unpack('>H', resp_len)
            data = await self._rfile_read(resp_len)
            data = self.__pskcipher.decrypt(data)

        data = io.BytesIO(data)

        resp_code = byte2int(data.read(1))
        if resp_code == 0:
            self.logger.debug('hxsocks read key exchange respond')
            pklen = byte2int(data.read(1))
            scertlen = byte2int(data.read(1))
            siglen = byte2int(data.read(1))

            server_key = data.read(pklen)
            auth = data.read(32)
            server_cert = data.read(scertlen)
            signature = data.read(siglen)

            # TODO: ask user if a certificate should be accepted or not.
            host, port = self.proxy._host_port
            server_id = '%s_%d' % (host, port)
            if server_id not in KNOWN_HOSTS:
                self.logger.info('hxs: server %s new cert %s saved.',
                                 server_id, hashlib.sha256(server_cert).hexdigest()[:8])
                with open('./.hxs_known_hosts/' + server_id + '.cert', 'wb') as f:
                    f.write(server_cert)
                    KNOWN_HOSTS[server_id] = server_cert
            elif KNOWN_HOSTS[server_id] != server_cert:
                self.logger.error('hxs: server %s certificate mismatch! PLEASE CHECK!', server_id)
                raise ConnectionResetError(0, 'hxs: bad certificate')

            if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(), hashlib.sha256).digest():
                try:
                    ECC.verify_with_pub_key(server_cert, auth, signature, self.hash_algo)
                    shared_secret = ecc.get_dh_key(server_key)
                    self.logger.debug('hxs key exchange success')
                    self.__cipher = AEncryptor(shared_secret, self.method, CTX)
                    # start reading from connection
                    asyncio.ensure_future(self.read_from_connection())
                    asyncio.ensure_future(self.stat())
                    self.connected = True
                    return
                except InvalidSignature:
                    self.logger.error('hxs getKey Error: server auth failed, bad signature')
            else:
                self.logger.error('hxs getKey Error: server auth failed, bad username or password')
        else:
            self.logger.error('hxs getKey Error. bad password or timestamp.')
        raise ConnectionResetError(0, 'hxs getKey Error')
Example #8
0
class Hxs2Connection:
    bufsize = 32768

    def __init__(self, proxy, timeout, manager):
        if not isinstance(proxy, ParentProxy):
            proxy = ParentProxy(proxy, proxy)
        self.logger = logging.getLogger('hxs2')
        self.proxy = proxy
        self.name = self.proxy.name
        self.timeout = timeout
        self._manager = manager
        self._ping_test = False
        self._ping_time = 0
        self.connected = False
        self.connection_lost = False

        self._psk = self.proxy.query.get('PSK', [''])[0]
        self.method = self.proxy.query.get('method', [DEFAULT_METHOD])[0].lower()
        self.hash_algo = self.proxy.query.get('hash', [DEFAULT_HASH])[0].upper()

        self.remote_reader = None
        self.remote_writer = None

        self.__pskcipher = None
        self.__cipher = None
        self._next_stream_id = 1

        self._client_writer = {}
        self._client_status = {}
        self._stream_status = {}
        self._last_active = {}
        self._last_active_c = time.monotonic()
        self._last_ping_log = 0

        self._last_direction = SEND
        self._last_count = 0
        self.send_delay = 0
        self.recv_intv = 1
        self.recv_time = 0
        self.recv_tp = 0
        self.recv_tp_ewma = 0

        self._stat_data_recv = 0
        self._stat_total_recv = 1
        self._stat_recv_tp = 0
        self._stat_data_sent = 0
        self._stat_total_sent = 1

        self._lock = Lock()

    async def connect(self, addr, port, timeout=3):
        self.logger.debug('hxsocks2 send connect request')
        self.timeout = timeout
        async with self._lock:
            if self.connection_lost:
                self._manager.remove(self)
                raise ConnectionResetError(0, 'hxs connection lost')
            if not self.connected:
                try:
                    await self.get_key()
                except asyncio.CancelledError:
                    raise
                except Exception as err:
                    self.logger.error('%s get_key %r', self.name, err)
                    # self.logger.error(traceback.format_exc())
                    try:
                        self.remote_writer.close()
                    except (OSError, AttributeError):
                        pass
                    raise ConnectionResetError(0, 'hxs get_key failed.')
        # send connect request
        payload = b''.join([chr(len(addr)).encode('latin1'),
                            addr.encode(),
                            struct.pack('>H', port),
                            b'\x00' * random.randint(64, 255),
                            ])
        stream_id = self._next_stream_id
        self._next_stream_id += 1
        if self._next_stream_id > MAX_STREAM_ID:
            self.logger.error('MAX_STREAM_ID reached')
            self._manager.remove(self)

        await self.send_frame(1, 0, stream_id, payload)

        # wait for server response
        event = Event()
        self._client_status[stream_id] = event

        # await event.wait()
        fut = event.wait()
        try:
            await asyncio.wait_for(fut, timeout=timeout)
        except asyncio.TimeoutError:
            self.logger.error('no response from %s, timeout=%.3f', self.name, timeout)
            del self._client_status[stream_id]
            self.print_status()
            await self.send_ping()
            raise

        del self._client_status[stream_id]

        if self._stream_status[stream_id] == OPEN:
            socketpair_a, socketpair_b = socket.socketpair()
            if sys.platform == 'win32':
                socketpair_a.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)
                socketpair_b.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)

            reader, writer = await asyncio.open_connection(sock=socketpair_b)
            writer.transport.set_write_buffer_limits(0, 0)

            self._client_writer[stream_id] = writer
            self._last_active[stream_id] = time.monotonic()
            # start forwarding
            asyncio.ensure_future(self.read_from_client(stream_id, reader))
            return socketpair_a
        raise ConnectionResetError(0, 'remote connect to %s:%d failed.' % (addr, port))

    async def read_from_client(self, stream_id, client_reader):
        self.logger.debug('start read from client')

        while not self.connection_lost:
            try:
                intv = 5
                fut = client_reader.read(self.bufsize)
                try:
                    data = await asyncio.wait_for(fut, timeout=intv)
                    self._last_active[stream_id] = time.monotonic()
                except asyncio.TimeoutError:
                    if time.monotonic() - self._last_active[stream_id] > 60 or\
                            self._stream_status[stream_id] & EOF_RECV:
                        await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256))
                        self._stream_status[stream_id] = CLOSED
                        break
                    continue
                except ConnectionResetError:
                    await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256))
                    self._stream_status[stream_id] = CLOSED
                    break

                if not data:
                    # close stream(LOCAL)
                    self._stream_status[stream_id] |= EOF_SENT
                    await self.send_frame(1, 1, stream_id, b'\x00' * random.randint(8, 256))
                    break

                elif self._stream_status[stream_id] & EOF_SENT:
                    self.logger.error('data recv from client, while stream is closed!')

                    await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256))
                    self._stream_status[stream_id] = CLOSED
                    self._client_writer[stream_id].close()
                    return
                payload = struct.pack('>H', len(data)) + data + b'\x00' * random.randint(8, 256)
                await self.send_frame(0, 0, stream_id, payload)
                self._stat_data_sent += len(data)
            except asyncio.CancelledError:
                raise
            except Exception as err:
                self.logger.error('CLIENT_SIDE BOOM! %r', err)
                self.logger.error(traceback.format_exc())
                self._stream_status[stream_id] = CLOSED
                break
        await asyncio.sleep(5)
        if self._stream_status[stream_id] != CLOSED:
            self._stream_status[stream_id] = CLOSED
            await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256))
        if stream_id in self._client_writer:
            try:
                self._client_writer[stream_id].close()
            except OSError:
                pass
            del self._client_writer[stream_id]

    async def send_frame(self, type_, flags, stream_id, payload):
        self.logger.debug('send_frame type: %d, stream_id: %d', type_, stream_id)
        if self.connection_lost:
            self.logger.error('send_frame: connection closed. %s', self.name)
            return
        if type_ != 6:
            self._last_active_c = time.monotonic()

        if self._last_direction == RECV:
            self._last_direction = SEND
            self._last_count = 0

        async with self._lock:
            try:
                header = struct.pack('>BBH', type_, flags, stream_id)
                data = header + payload
                ct = self.__cipher.encrypt(data)
                self.remote_writer.write(struct.pack('>H', len(ct)) + ct)
                await self.remote_writer.drain()
                self._stat_total_sent += len(ct) + 2
                self._last_count += 1
            except ConnectionResetError:
                self.connection_lost = True
                self._manager.remove(self)
            else:
                if type_ == 0 and self._last_count > 10 and random.random() < 0.01:
                    asyncio.ensure_future(self.send_ping(False))

    async def send_ping(self, test=True):
        if self._ping_time == 0:
            self._ping_test = test
            self._ping_time = time.monotonic()
            await self.send_frame(6, 0, 0, b'\x00' * random.randint(64, 256))

    async def read_from_connection(self):
        self.logger.debug('start read from connection')
        last_recv = time.monotonic()
        while not self.connection_lost:
            try:
                # read frame_len
                intv = 3 if self._ping_test else 6

                try:
                    frame_len = await self._rfile_read(2, timeout=intv)
                    frame_len, = struct.unpack('>H', frame_len)
                except asyncio.TimeoutError:
                    if self._ping_test:
                        self.logger.warning('server no response %s', self.proxy.name)
                        break
                    if time.monotonic() - self._last_active_c > 120:
                        # no point keeping so long
                        break
                    if time.monotonic() - self._last_active_c > 10:
                        await self.send_ping()
                    continue
                except Exception as err:
                    # destroy connection
                    self.logger.error('read from connection error: %r', err)
                    break
                finally:
                    # log recv delay
                    recv_intv = time.monotonic() - last_recv
                    last_recv = time.monotonic()
                    self.recv_intv = self.recv_intv * 0.87 + recv_intv * 0.13

                # read frame_data
                try:
                    frame_data = await self._rfile_read(frame_len, timeout=self.timeout)
                    frame_data = self.__cipher.decrypt(frame_data)
                    self._stat_total_recv += frame_len + 2
                    self._stat_recv_tp += frame_len + 2
                except (asyncio.TimeoutError, InvalidTag) as err:
                    # destroy connection
                    self.logger.error('read frame data error: %r', err)
                    break
                else:
                    recv_time = time.monotonic() - last_recv
                    self.recv_time = self.recv_time * 0.87 + recv_time * 0.13

                # parse chunk_data
                # +------+-------------------+----------+
                # | type | flags | stream_id | payload  |
                # +------+-------------------+----------+
                # |  1   |   1   |     2     | Variable |
                # +------+-------------------+----------+

                header, payload = frame_data[:4], frame_data[4:]
                frame_type, frame_flags, stream_id = struct.unpack('>BBH', header)
                payload = io.BytesIO(payload)
                self.logger.debug('recv frame_type: %s, stream_id: %s', frame_type, stream_id)

                if self._last_direction == SEND:
                    self._last_direction = RECV
                    self._last_count = 0
                self._last_count += 1

                if self._last_count > 10 and random.random() < 0.01:
                    await self.send_frame(6, 1, 0, b'\x00' * random.randint(64, 256))

                if frame_type == 0:
                    # DATA
                    # first 2 bytes of payload indicates data_len, the rest would be padding
                    self._last_active_c = time.monotonic()
                    data_len, = struct.unpack('>H', payload.read(2))
                    data = payload.read(data_len)
                    if len(data) != data_len:
                        # something went wrong, destory connection
                        self.logger.error('len(data) != data_len')
                        break
                    # sent data to stream
                    try:
                        self._last_active[stream_id] = time.monotonic()
                        self._client_writer[stream_id].write(data)
                        await self._client_writer[stream_id].drain()
                        self._stat_data_recv += data_len
                    except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
                        # client error, reset stream
                        self._client_writer[stream_id].close()
                        self._stream_status[stream_id] = CLOSED
                        await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256))
                    except KeyError:
                        self._stream_status[stream_id] = CLOSED
                        await self.send_frame(3, 0, stream_id, b'\x00' * random.randint(8, 256))
                elif frame_type == 1:
                    # HEADER
                    self._last_active_c = time.monotonic()
                    if self._next_stream_id == stream_id:
                        # server is not supposed to open a new stream
                        # send connection error?
                        break
                    elif stream_id < self._next_stream_id:
                        if frame_flags == END_STREAM_FLAG:
                            self._stream_status[stream_id] |= EOF_RECV
                            try:
                                self._client_writer[stream_id].write_eof()  # KeyError?
                            except (KeyError, AttributeError, ConnectionResetError):
                                pass
                            if self._stream_status[stream_id] == CLOSED:
                                if stream_id in self._client_writer:
                                    self._client_writer[stream_id].close()
                                    del self._client_writer[stream_id]
                        else:
                            # confirm a stream is opened
                            if stream_id in self._client_status:
                                self._stream_status[stream_id] = OPEN
                                self._client_status[stream_id].set()
                            else:
                                self.logger.info('%s stream open, client closed', self.name)
                                self._stream_status[stream_id] = CLOSED
                                await self.send_frame(3, 0, stream_id,
                                                      b'\x00' * random.randint(8, 256))
                elif frame_type == 3:
                    # RST_STREAM
                    self._last_active_c = time.monotonic()
                    self._stream_status[stream_id] = CLOSED
                    if stream_id in self._client_writer:
                        self._client_writer[stream_id].close()
                        del self._client_writer[stream_id]

                elif frame_type == 6:
                    # PING
                    if frame_flags == 1:
                        # pong
                        if time.monotonic() - self._last_ping_log > 30:
                            resp_time = time.monotonic() - self._ping_time
                            self.logger.info('server response time: %.3f %s', resp_time, self.proxy.name)
                            self._last_ping_log = time.monotonic()
                        self._ping_test = False
                        self._ping_time = 0
                    else:
                        await self.send_frame(6, 1, 0, b'\x00' * random.randint(64, 256))
                elif frame_type == 7:
                    # GOAWAY
                    # no more new stream
                    max_stream_id = payload.read(2)
                    self._manager.remove(self)
                    for stream_id, client_writer in self._client_writer:
                        if stream_id > max_stream_id:
                            # reset stream
                            try:
                                client_writer.close()
                            except OSError:
                                pass
                elif frame_type == 8:
                    # WINDOW_UPDATE
                    pass
                else:
                    break
            except Exception as err:
                self.logger.error('CONNECTION BOOM! %r', err)
                self.logger.error(traceback.format_exc())
                break
        # out of loop, destroy connection
        self.connection_lost = True
        self.logger.warning('out of loop %s', self.proxy.name)
        self.logger.info('total_recv: %d, data_recv: %d %.3f',
                         self._stat_total_recv, self._stat_data_recv,
                         self._stat_data_recv / self._stat_total_recv)
        self.logger.info('total_sent: %d, data_sent: %d %.3f',
                         self._stat_total_sent, self._stat_data_sent,
                         self._stat_data_sent / self._stat_total_sent)
        self._manager.remove(self)

        for sid, status in self._client_status.items():
            if isinstance(status, Event):
                self._stream_status[sid] = CLOSED
                status.set()

        try:
            self.remote_writer.close()
        except (OSError, IOError):
            pass
        for stream_id, writer in self._client_writer.items():
            try:
                writer.close()
            except ConnectionResetError:
                pass

    async def get_key(self):
        self.logger.debug('hxsocks2 getKey')
        usn, psw = (self.proxy.username, self.proxy.password)
        self.logger.info('%s connect to server', self.name)
        from .connection import open_connection
        self.remote_reader, self.remote_writer, _ = await open_connection(
            self.proxy.hostname,
            self.proxy.port,
            proxy=self.proxy.get_via(),
            timeout=self.timeout,
            tunnel=True)

        # prep key exchange request
        self.__pskcipher = Encryptor(self._psk, self.method)
        ecc = ECC(self.__pskcipher._key_len)
        pubk = ecc.get_pub_key()
        ts = int(time.time()) // 30
        ts = struct.pack('>I', ts)
        padding_len = random.randint(64, 255)
        data = b''.join([chr(len(pubk)).encode('latin1'),
                         pubk,
                         hmac.new(psw.encode(), ts + pubk + usn.encode(), hashlib.sha256).digest(),
                         b'\x00' * padding_len])
        data = chr(20).encode() + struct.pack('>H', len(data)) + data

        ct = self.__pskcipher.encrypt(data)

        # send key exchange request
        self.remote_writer.write(ct)
        await self.remote_writer.drain()

        # read iv
        iv = await self._rfile_read(self.__pskcipher._iv_len, self.timeout)
        self.__pskcipher.decrypt(iv)

        # read server response
        if is_aead(self.method):
            ct_len = await self._rfile_read(18, self.timeout)
            ct_len = self.__pskcipher.decrypt(ct_len)
            ct_len, = struct.unpack('!H', ct_len)
            ct = await self._rfile_read(ct_len + 16)
            ct = self.__pskcipher.decrypt(ct)
            data = ct[2:]
        else:
            resp_len = await self._rfile_read(2, self.timeout)
            resp_len = self.__pskcipher.decrypt(resp_len)
            resp_len, = struct.unpack('>H', resp_len)
            data = await self._rfile_read(resp_len)
            data = self.__pskcipher.decrypt(data)

        data = io.BytesIO(data)

        resp_code = byte2int(data.read(1))
        if resp_code == 0:
            self.logger.debug('hxsocks read key exchange respond')
            pklen = byte2int(data.read(1))
            scertlen = byte2int(data.read(1))
            siglen = byte2int(data.read(1))

            server_key = data.read(pklen)
            auth = data.read(32)
            server_cert = data.read(scertlen)
            signature = data.read(siglen)

            # TODO: ask user if a certificate should be accepted or not.
            host, port = self.proxy._host_port
            server_id = '%s_%d' % (host, port)
            if server_id not in KNOWN_HOSTS:
                self.logger.info('hxs: server %s new cert %s saved.',
                                 server_id, hashlib.sha256(server_cert).hexdigest()[:8])
                with open('./.hxs_known_hosts/' + server_id + '.cert', 'wb') as f:
                    f.write(server_cert)
                    KNOWN_HOSTS[server_id] = server_cert
            elif KNOWN_HOSTS[server_id] != server_cert:
                self.logger.error('hxs: server %s certificate mismatch! PLEASE CHECK!', server_id)
                raise ConnectionResetError(0, 'hxs: bad certificate')

            if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(), hashlib.sha256).digest():
                try:
                    ECC.verify_with_pub_key(server_cert, auth, signature, self.hash_algo)
                    shared_secret = ecc.get_dh_key(server_key)
                    self.logger.debug('hxs key exchange success')
                    self.__cipher = AEncryptor(shared_secret, self.method, CTX)
                    # start reading from connection
                    asyncio.ensure_future(self.read_from_connection())
                    asyncio.ensure_future(self.stat())
                    self.connected = True
                    return
                except InvalidSignature:
                    self.logger.error('hxs getKey Error: server auth failed, bad signature')
            else:
                self.logger.error('hxs getKey Error: server auth failed, bad username or password')
        else:
            self.logger.error('hxs getKey Error. bad password or timestamp.')
        raise ConnectionResetError(0, 'hxs getKey Error')

    async def _rfile_read(self, size, timeout=3):
        fut = self.remote_reader.readexactly(size)
        data = await asyncio.wait_for(fut, timeout=timeout)
        return data

    def count(self):
        return len(self._client_writer)

    async def stat(self):
        while not self.connection_lost:
            await asyncio.sleep(1)
            self.recv_tp = self._stat_recv_tp
            self.recv_tp_ewma = self.recv_tp_ewma * 0.87 + self._stat_recv_tp * 0.13
            self._stat_recv_tp = 0

    def is_busy(self):
        if self.recv_time > self.recv_intv * 0.8:
            return True
        return False

    def print_status(self):
        self.logger.info('%s recv_tp: %s', self.name, self.recv_tp)
        self.logger.info('%s tp_ewma: %d', self.name, self.recv_tp_ewma)
        self.logger.info('%s recv_intv: %f', self.name, self.recv_intv)
        self.logger.info('%s recv_time: %f', self.name, self.recv_time)
        self.logger.info('%s stream: %d', self.name, self.count())
Example #9
0
class Hxs2Connection:
    bufsize = 65535 - 22

    def __init__(self, proxy, manager):
        if not isinstance(proxy, ParentProxy):
            proxy = ParentProxy(proxy, proxy)
        self.logger = logging.getLogger('hxs2')
        self.proxy = proxy
        self.name = self.proxy.name
        self.timeout = 6  # read frame_data timeout
        self._manager = manager
        self._ping_test = False
        self._ping_time = 0
        self.connected = False
        self.connection_lost = False
        self.udp_relay_support = None
        self.udp_event = None

        self._psk = self.proxy.query.get('PSK', [''])[0]
        self.method = self.proxy.query.get('method',
                                           [DEFAULT_METHOD])[0].lower()
        self.mode = int(self.proxy.query.get('mode', ['0'])[0])
        self.hash_algo = self.proxy.query.get('hash',
                                              [DEFAULT_HASH])[0].upper()

        self.remote_reader = None
        self.remote_writer = None
        self._socport = None

        self.__pskcipher = None
        self.__cipher = None
        self._next_stream_id = 1
        self._settings_async_drain = None

        self._client_writer = {}
        self._client_status = {}
        self._client_resume_reading = {}
        self._client_drain_lock = {}
        self._stream_status = {}
        self._stream_addr = {}
        self._stream_task = {}
        self._last_active = {}
        self._last_active_c = time.monotonic()
        self._last_ping_log = 0
        self._connection_task = None
        self._connection_stat = None

        self._last_direction = SEND
        self._last_count = 0
        self._buffer_size_ewma = 0
        self._recv_tp_max = 0
        self._recv_tp_ewma = 0
        self._sent_tp_max = 0
        self._sent_tp_ewma = 0

        self._stat_data_recv = 0
        self._stat_total_recv = 1
        self._stat_recv_tp = 0
        self._stat_data_sent = 0
        self._stat_total_sent = 1
        self._stat_sent_tp = 0

        self._lock = Lock()

    async def connect(self, addr, port, timeout=3):
        self.logger.debug('hxsocks2 send connect request')
        if self.connection_lost:
            self._manager.remove(self)
            raise ConnectionLostError(0, 'hxs2 connection lost')
        if not self.connected:
            self._manager.remove(self)
            raise ConnectionResetError(0, 'hxs2 not connected.')
        # send connect request
        payload = b''.join([
            chr(len(addr)).encode('latin1'),
            addr.encode(),
            struct.pack('>H', port),
            b'\x00' * random.randint(64, 255),
        ])
        stream_id = self._next_stream_id
        self._next_stream_id += 1
        if self._next_stream_id > MAX_STREAM_ID:
            self.logger.error('MAX_STREAM_ID reached')
            self._manager.remove(self)

        self.send_frame(HEADERS, OPEN, stream_id, payload)
        self._stream_addr[stream_id] = (addr, port)

        # wait for server response
        event = Event()
        self._client_status[stream_id] = event
        self._client_resume_reading[stream_id] = asyncio.Event()
        self._client_resume_reading[stream_id].set()

        # await event.wait()
        fut = event.wait()
        try:
            await asyncio.wait_for(fut, timeout=timeout)
        except asyncio.TimeoutError:
            self.logger.error('no response from %s, timeout=%.3f', self.name,
                              timeout)
            del self._client_status[stream_id]
            self.print_status()
            self.send_ping()
            raise

        del self._client_status[stream_id]

        if self._stream_status[stream_id] == OPEN:
            socketpair_a, socketpair_b = socket.socketpair()
            if sys.platform == 'win32':
                socketpair_a.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY,
                                        1)
                socketpair_b.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY,
                                        1)
            reader, writer = await asyncio.open_connection(sock=socketpair_b,
                                                           limit=131072)
            writer.transport.set_write_buffer_limits(CLIENT_WRITE_BUFFER)

            self._client_writer[stream_id] = writer
            self._last_active[stream_id] = time.monotonic()
            self._client_drain_lock[stream_id] = asyncio.Lock()
            # start forwarding
            self._stream_task[stream_id] = asyncio.ensure_future(
                self.read_from_client(stream_id, reader))
            return socketpair_a
        raise ConnectionResetError(
            0, 'remote connect to %s:%d failed.' % (addr, port))

    async def read_from_client(self, stream_id, client_reader):
        self.logger.debug('start read from client')

        while not self.connection_lost:
            await self._client_resume_reading[stream_id].wait()
            fut = client_reader.read(self.bufsize)
            try:
                data = await asyncio.wait_for(fut, timeout=6)
                self._last_active[stream_id] = time.monotonic()
            except asyncio.TimeoutError:
                if time.monotonic() - self._last_active[stream_id] < STREAM_TIMEOUT and\
                        self._stream_status[stream_id] == OPEN:
                    continue
                data = b''
            except ConnectionError:
                await self.close_stream(stream_id)
                return

            if not data:
                # close stream(LOCAL)
                self._stream_status[stream_id] |= EOF_SENT
                self.send_frame(HEADERS, END_STREAM_FLAG, stream_id,
                                bytes(random.randint(8, 256)))
                break

            if self._stream_status[stream_id] & EOF_SENT:
                self.logger.error(
                    'data recv from client, while stream is closed!')
                await self.close_stream(stream_id)
                return
            await self.send_data_frame(stream_id, data)
        while time.monotonic() - self._last_active[stream_id] < 12:
            await asyncio.sleep(6)
        await self.close_stream(stream_id)

    def send_frame(self, type_, flags, stream_id, payload):
        self.logger.debug('send_frame type: %d, stream_id: %d', type_,
                          stream_id)
        if self.connection_lost:
            self.logger.error('send_frame: connection closed. %s', self.name)
            return
        if type_ != PING:
            self._last_active_c = time.monotonic()
        elif flags == 0:
            self._ping_time = time.monotonic()

        if self._last_direction == RECV:
            self._last_direction = SEND
            self._last_count = 0

        header = struct.pack('>BBH', type_, flags, stream_id)
        data = header + payload
        ct = self.__cipher.encrypt(data)
        self.remote_writer.write(struct.pack('>H', len(ct)) + ct)
        self._stat_total_sent += len(ct) + 2
        self._stat_sent_tp += len(ct) + 2
        self._last_count += 1

        if self._settings_async_drain is None and random.random() < 0.1:
            self._settings_async_drain = False
            self.send_frame(SETTINGS, 0, 1, bytes(random.randint(64, 256)))
        if type_ == DATA and self._last_count > 10 and random.random() < 0.01:
            self.send_ping(False)

    def send_ping(self, test=True):
        if self._ping_time == 0:
            self._ping_test = test
            self.send_frame(PING, 0, 0, bytes(random.randint(64, 256)))

    def send_one_data_frame(self, stream_id, data):
        payload = struct.pack('>H', len(data)) + data
        diff = self.bufsize - len(data)
        payload += bytes(random.randint(min(diff, 8), min(diff, 255)))
        self.send_frame(DATA, 0, stream_id, payload)

    async def send_data_frame(self, stream_id, data):
        data_len = len(data)
        if data_len > 16386 and random.random() < 0.1:
            data = io.BytesIO(data)
            data_ = data.read(random.randint(256, 16386 - 22))
            while data_:
                self.send_one_data_frame(stream_id, data_)
                if random.random() < 0.1:
                    self.send_frame(PING, 0, 0,
                                    bytes(random.randint(256, 1024)))
                data_ = data.read(random.randint(256, 8192 - 22))
                await asyncio.sleep(0)
        else:
            self.send_one_data_frame(stream_id, data)
        self._stat_data_sent += data_len
        buffer_size = self.remote_writer.transport.get_write_buffer_size()
        self._buffer_size_ewma = self._buffer_size_ewma * 0.87 + buffer_size * 0.13
        await self.drain()

    async def drain(self):
        async with self._lock:
            if self.connection_lost:
                return
            try:
                await self.remote_writer.drain()
            except ConnectionError:
                self.connection_lost = True

    async def udp_associate(self, udp_server):
        if self.connection_lost:
            self._manager.remove(self)
            raise ConnectionLostError(0, 'hxs2 connection lost')
        if not self.connected:
            self._manager.remove(self)
            raise ConnectionResetError(0, 'hxs2 not connected.')
        if self.udp_relay_support is None:
            # ask server for udp_support
            self.send_frame(UDP_ASSOCIATE, OPEN, 0,
                            bytes(random.randint(64, 256)))
            if not self.udp_event:
                self.udp_event = Event()
            fut = self.udp_event.wait()
            try:
                await asyncio.wait_for(fut, timeout=self.timeout)
            except asyncio.TimeoutError:
                if not self.udp_relay_support:
                    self.udp_relay_support = False
                    self.logger.error(
                        '%s does not seem to support UDP_ASSOCIATE, timeout: %d',
                        self.name, self.timeout)
        if not self.udp_relay_support:
            raise ConnectionResetError(
                0, '%s does not seem to support UDP_ASSOCIATE' % self.name)
        # send udp_assicoate request
        stream_id = self._next_stream_id
        self._next_stream_id += 1
        if self._next_stream_id > MAX_STREAM_ID:
            self.logger.error('MAX_STREAM_ID reached')
            self._manager.remove(self)

        self.send_frame(UDP_ASSOCIATE, OPEN, stream_id,
                        bytes(random.randint(64, 256)))
        self._stream_status[stream_id] = OPEN
        self._client_resume_reading[stream_id] = asyncio.Event()
        self._client_resume_reading[stream_id].set()
        relay = UDPRelayHxs2(udp_server, stream_id, self)
        self._client_writer[stream_id] = relay
        return relay

    async def read_from_connection(self):
        self.logger.debug('start read from connection')
        while not self.connection_lost:
            try:
                # read frame_len
                intv = 2 if self._ping_test else 6

                try:
                    frame_len = await self._rfile_read(2, timeout=intv)
                    frame_len, = struct.unpack('>H', frame_len)
                except asyncio.TimeoutError:
                    if self._ping_test and time.monotonic(
                    ) - self._ping_time > 6:
                        self.logger.warning('server no response %s',
                                            self.proxy.name)
                        break
                    if time.monotonic() - self._last_active_c > CONN_TIMEOUT:
                        # no point keeping so long
                        break
                    if time.monotonic() - self._last_active_c > 10:
                        if not self._ping_test:
                            self.send_ping()
                    continue
                except (ConnectionError, asyncio.IncompleteReadError) as err:
                    # destroy connection
                    self.logger.error('read from connection error: %r', err)
                    break

                # read frame_data
                try:
                    frame_data = await self._rfile_read(frame_len,
                                                        timeout=self.timeout)
                    frame_data = self.__cipher.decrypt(frame_data)
                    self._stat_total_recv += frame_len + 2
                    self._stat_recv_tp += frame_len + 2
                except (ConnectionError, asyncio.TimeoutError,
                        asyncio.IncompleteReadError, InvalidTag) as err:
                    # destroy connection
                    self.logger.error('read frame data error: %r, timeout %s',
                                      err, self.timeout)
                    break

                if self._last_direction == SEND:
                    self._last_direction = RECV
                    self._last_count = 0
                self._last_count += 1

                if self._last_count > 10 and random.random() < 0.1:
                    self.send_frame(PING, PONG, 0,
                                    bytes(random.randint(64, 256)))

                # parse chunk_data
                # +------+-------------------+----------+
                # | type | flags | stream_id | payload  |
                # +------+-------------------+----------+
                # |  1   |   1   |     2     | Variable |
                # +------+-------------------+----------+

                header, payload = frame_data[:4], frame_data[4:]
                frame_type, frame_flags, stream_id = struct.unpack(
                    '>BBH', header)
                payload = io.BytesIO(payload)
                self.logger.debug(
                    'recv frame_type: %s, stream_id: %s, size: %s', frame_type,
                    stream_id, frame_len)

                if frame_type == DATA:  # 0
                    self._last_active_c = time.monotonic()
                    if self._stream_status[stream_id] & EOF_RECV:
                        # from server send buffer
                        self.logger.debug(
                            'DATA recv Stream CLOSED, status: %s',
                            self._stream_status[stream_id])
                        continue
                    # first 2 bytes of payload indicates data_len, the rest would be padding
                    data_len, = struct.unpack('>H', payload.read(2))
                    data = payload.read(data_len)
                    if len(data) != data_len:
                        # something went wrong, destory connection
                        self.logger.error('len(data) != data_len')
                        break

                    # sent data to stream
                    try:
                        self._last_active[stream_id] = time.monotonic()
                        if isinstance(self._client_writer[stream_id],
                                      UDPRelayHxs2):
                            await self._client_writer[stream_id
                                                      ].on_remote_recv(data)
                        else:
                            self._client_writer[stream_id].write(data)
                            await self.client_writer_drain(stream_id)
                        self._stat_data_recv += data_len
                    except OSError:
                        # client error, reset stream
                        asyncio.ensure_future(self.close_stream(stream_id))
                elif frame_type == HEADERS:  # 1
                    self._last_active_c = time.monotonic()
                    if self._next_stream_id == stream_id:
                        # server is not supposed to open a new stream
                        # send connection error?
                        break
                    if stream_id < self._next_stream_id:
                        if frame_flags == END_STREAM_FLAG:
                            self._stream_status[stream_id] |= EOF_RECV
                            if stream_id in self._client_writer:
                                try:
                                    self._client_writer[stream_id].write_eof()
                                except OSError:
                                    self._stream_status[stream_id] = CLOSED
                            if self._stream_status[stream_id] == CLOSED:
                                asyncio.ensure_future(
                                    self.close_stream(stream_id))
                        else:
                            # confirm a stream is opened
                            if stream_id in self._client_status:
                                self._stream_status[stream_id] = OPEN
                                self._client_status[stream_id].set()
                            else:
                                addr = '%s:%s' % self._stream_addr[stream_id]
                                self.logger.info(
                                    '%s stream open, client closed, %s',
                                    self.name, addr)
                                self._stream_status[stream_id] = CLOSED
                                self.send_frame(RST_STREAM, 0, stream_id,
                                                bytes(random.randint(8, 256)))
                elif frame_type == RST_STREAM:  # 3
                    self._last_active_c = time.monotonic()
                    self._stream_status[stream_id] = CLOSED
                    if stream_id in self._client_status:
                        self._client_status[stream_id].set()
                    asyncio.ensure_future(self.close_stream(stream_id))
                elif frame_type == SETTINGS:
                    if stream_id == 1:
                        self._settings_async_drain = True
                elif frame_type == PING:  # 6
                    if frame_flags == PONG:
                        resp_time = time.monotonic() - self._ping_time
                        if time.monotonic() - self._last_ping_log > 30:
                            self.logger.info('server response time: %.3f %s',
                                             resp_time, self.proxy.name)
                            self._last_ping_log = time.monotonic()
                            if resp_time < 0.5:
                                self.proxy.log('', resp_time)
                        self._ping_test = False
                        self._ping_time = 0
                    else:
                        self.send_frame(PING, PONG, 0,
                                        bytes(random.randint(64, 2048)))
                elif frame_type == GOAWAY:  # 7
                    # no more new stream
                    max_stream_id = payload.read(2)
                    self._manager.remove(self)
                    for stream_id, client_writer in self._client_writer:
                        if stream_id > max_stream_id:
                            # reset stream
                            client_writer.close()
                            try:
                                await client_writer.wait_closed()
                            except ConnectionError:
                                pass
                elif frame_type == WINDOW_UPDATE:  # 8
                    if frame_flags == 1:
                        self._client_resume_reading[stream_id].clear()
                    else:
                        self._client_resume_reading[stream_id].set()
                elif frame_type == UDP_ASSOCIATE:  # 20
                    if stream_id == 0:
                        self.udp_relay_support = True
                        if self.udp_event:
                            self.udp_event.set()
            except Exception as err:
                self.logger.error('CONNECTION BOOM! %r', err)
                self.logger.error(traceback.format_exc())
                break
        # out of loop, destroy connection
        self.connection_lost = True
        self._manager.remove(self)
        self.logger.warning('out of loop %s', self.proxy.name)
        self.logger.info('total_recv: %d, data_recv: %d %.3f',
                         self._stat_total_recv, self._stat_data_recv,
                         self._stat_data_recv / self._stat_total_recv)
        self.logger.info('total_sent: %d, data_sent: %d %.3f',
                         self._stat_total_sent, self._stat_data_sent,
                         self._stat_data_sent / self._stat_total_sent)
        self.print_status()

        for sid, status in self._client_status.items():
            if isinstance(status, Event):
                self._stream_status[sid] = CLOSED
                status.set()

        task_list = []
        for stream_id in self._client_writer:
            self._stream_status[stream_id] = CLOSED
            if not self._client_writer[stream_id].is_closing():
                self._client_writer[stream_id].close()
                task_list.append(self._client_writer[stream_id])
        self._client_writer = {}
        task_list = [asyncio.create_task(w.wait_closed()) for w in task_list]
        task_list.append(asyncio.create_task(self.close()))
        if task_list:
            await asyncio.wait(task_list)

    async def get_key(self, timeout, tcp_nodelay):
        self.logger.debug('hxsocks2 getKey')
        usn, psw = (self.proxy.username, self.proxy.password)
        self.logger.info('%s connect to server', self.name)
        from .connection import open_connection
        self.remote_reader, self.remote_writer, _ = await open_connection(
            self.proxy.hostname,
            self.proxy.port,
            proxy=self.proxy.get_via(),
            timeout=timeout,
            tunnel=True,
            limit=262144,
            tcp_nodelay=tcp_nodelay)
        self.remote_writer.transport.set_write_buffer_limits(262144)

        # prep key exchange request
        self.__pskcipher = Encryptor(self._psk, self.method)
        ecc = ECC(self.__pskcipher.key_len)
        pubk = ecc.get_pub_key()
        timestamp = struct.pack('>I', int(time.time()) // 30)
        data = b''.join([
            chr(len(pubk)).encode('latin1'), pubk,
            hmac.new(psw.encode(), timestamp + pubk + usn.encode(),
                     hashlib.sha256).digest(),
            bytes((self.mode, )),
            bytes(random.randint(64, 450))
        ])
        data = chr(20).encode() + struct.pack('>H', len(data)) + data

        ct = self.__pskcipher.encrypt(data)

        # send key exchange request
        self.remote_writer.write(ct)
        await self.remote_writer.drain()

        # read iv
        iv_ = await self._rfile_read(self.__pskcipher.iv_len, timeout)
        self.__pskcipher.decrypt(iv_)

        # read server response
        if is_aead(self.method):
            ct_len = await self._rfile_read(18, timeout)
            ct_len = self.__pskcipher.decrypt(ct_len)
            ct_len, = struct.unpack('!H', ct_len)
            ct_ = await self._rfile_read(ct_len + 16)
            ct_ = self.__pskcipher.decrypt(ct_)
            data = ct_[2:]
        else:
            resp_len = await self._rfile_read(2, timeout)
            resp_len = self.__pskcipher.decrypt(resp_len)
            resp_len, = struct.unpack('>H', resp_len)
            data = await self._rfile_read(resp_len)
            data = self.__pskcipher.decrypt(data)

        data = io.BytesIO(data)

        resp_code = byte2int(data.read(1))
        if resp_code == 0:
            self.logger.debug('hxsocks read key exchange respond')
            pklen = byte2int(data.read(1))
            scertlen = byte2int(data.read(1))
            siglen = byte2int(data.read(1))

            server_key = data.read(pklen)
            auth = data.read(32)
            server_cert = data.read(scertlen)
            signature = data.read(siglen)
            mode = data.read(1)[0]

            # TODO: ask user if a certificate should be accepted or not.
            host, port = self.proxy._host_port
            server_id = '%s_%d' % (host, port)
            if server_id not in KNOWN_HOSTS:
                self.logger.info('hxs: server %s new cert %s saved.',
                                 server_id,
                                 hashlib.sha256(server_cert).hexdigest()[:8])
                with open('./.hxs_known_hosts/' + server_id + '.cert',
                          'wb') as f:
                    f.write(server_cert)
                    KNOWN_HOSTS[server_id] = server_cert
            elif KNOWN_HOSTS[server_id] != server_cert:
                self.logger.error(
                    'hxs: server %s certificate mismatch! PLEASE CHECK!',
                    server_id)
                raise ConnectionResetError(0, 'hxs: bad certificate')

            if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(),
                                hashlib.sha256).digest():
                try:
                    ECC.verify_with_pub_key(server_cert, auth, signature,
                                            self.hash_algo)
                    shared_secret = ecc.get_dh_key(server_key)
                    self.logger.debug('hxs key exchange success')
                    if mode == 1:
                        self.__cipher = EncryptorStream(shared_secret,
                                                        'rc4-md5',
                                                        check_iv=False)
                    else:
                        self.__cipher = AEncryptor(shared_secret,
                                                   self.method,
                                                   CTX,
                                                   check_iv=False)
                    # start reading from connection
                    self._connection_task = asyncio.ensure_future(
                        self.read_from_connection())
                    self._connection_stat = asyncio.ensure_future(self.stat())
                    self.connected = True
                    self._socport = self.remote_writer.get_extra_info(
                        'sockname')[1]
                    return
                except InvalidSignature:
                    self.logger.error(
                        'hxs getKey Error: server auth failed, bad signature')
            else:
                self.logger.error(
                    'hxs getKey Error: server auth failed, bad username or password'
                )
        else:
            self.logger.error('hxs getKey Error. bad password or timestamp.')
        raise ConnectionResetError(0, 'hxs getKey Error')

    async def _rfile_read(self, size, timeout=3):
        fut = self.remote_reader.readexactly(size)
        data = await asyncio.wait_for(fut, timeout=timeout)
        return data

    def count(self):
        return len(self._client_writer)

    async def stat(self):
        while not self.connection_lost:
            await asyncio.sleep(1)
            self._recv_tp_ewma = self._recv_tp_ewma * 0.8 + self._stat_recv_tp * 0.2
            if self._recv_tp_ewma > self._recv_tp_max:
                self._recv_tp_max = self._recv_tp_ewma
            self._stat_recv_tp = 0
            self._sent_tp_ewma = self._sent_tp_ewma * 0.8 + self._stat_sent_tp * 0.2
            if self._sent_tp_ewma > self._sent_tp_max:
                self._sent_tp_max = self._sent_tp_ewma
            self._stat_sent_tp = 0
            buffer_size = self.remote_writer.transport.get_write_buffer_size()
            self._buffer_size_ewma = self._buffer_size_ewma * 0.8 + buffer_size * 0.2

    def busy(self):
        return self._recv_tp_ewma + self._sent_tp_ewma

    def is_busy(self):
        return self._buffer_size_ewma > 2048 or \
            (self._recv_tp_max > 524288 and self._recv_tp_ewma > self._recv_tp_max * 0.3) or \
            (self._sent_tp_max > 262144 and self._sent_tp_ewma > self._sent_tp_max * 0.3)

    def print_status(self):
        if not self.connected:
            return
        self.logger.info('%s:%s status:', self.name, self._socport)
        self.logger.info('recv_tp_max: %8d, ewma: %8d', self._recv_tp_max,
                         self._recv_tp_ewma)
        self.logger.info('sent_tp_max: %8d, ewma: %8d', self._sent_tp_max,
                         self._sent_tp_ewma)
        self.logger.info('buffer_ewma: %8d, stream: %6d',
                         self._buffer_size_ewma, self.count())

    async def close_stream(self, stream_id):
        if not self._client_resume_reading[stream_id].is_set():
            self._client_resume_reading[stream_id].set()
        if self._stream_status[stream_id] != CLOSED:
            self.send_frame(RST_STREAM, 0, stream_id,
                            bytes(random.randint(8, 256)))
            self._stream_status[stream_id] = CLOSED
        if stream_id in self._client_writer:
            writer = self._client_writer[stream_id]
            del self._client_writer[stream_id]
            if not writer.is_closing():
                writer.close()
            try:
                await writer.wait_closed()
            except ConnectionError:
                pass

    async def client_writer_drain(self, stream_id):
        if self._settings_async_drain:
            asyncio.ensure_future(self.async_drain(stream_id))
        else:
            await self._client_writer[stream_id].drain()

    async def async_drain(self, stream_id):
        if isinstance(self._client_writer[stream_id], UDPRelayHxs2):
            return
        wbuffer_size = self._client_writer[
            stream_id].transport.get_write_buffer_size()
        if wbuffer_size <= CLIENT_WRITE_BUFFER:
            return

        async with self._client_drain_lock[stream_id]:
            try:
                # tell client to stop reading
                self.send_frame(WINDOW_UPDATE, 1, stream_id,
                                bytes(random.randint(64, 256)))
                await self._client_writer[stream_id].drain()
                # tell client to resume reading
                self.send_frame(WINDOW_UPDATE, 0, stream_id,
                                bytes(random.randint(64, 256)))
            except OSError:
                await self.close_stream(stream_id)
                return

    async def close(self):
        if self.remote_writer:
            if not self.remote_writer.is_closing():
                self.remote_writer.close()
            try:
                await self.remote_writer.wait_closed()
            except OSError:
                pass
Example #10
0
class HXsocksHandler:
    bufsize = 32768

    def __init__(self, server):
        self.server = server
        self.logger = server.logger
        self.user_mgr = self.server.user_mgr
        self.address = self.server.address

        self.encryptor = Encryptor(self.server.psk, self.server.method)
        self._buf = b''

        self.client_address = None
        self.client_reader = None

    async def _read(self, size=None):
        if self.server.aead:
            _len = await self.client_reader.readexactly(18)
            if not _len:
                return b''
            _len = self.encryptor.decrypt(_len)
            _len, = struct.unpack("!H", _len)
            ct = await self.client_reader.readexactly(_len + 16)
            if not ct:
                return b''
        else:
            size = size or self.bufsize
            ct = await self.client_reader.read(size)
        return self.encryptor.decrypt(ct)

    async def read(self, size=None):
        # compatible with shadowsocks aead
        if not size:
            if self._buf:
                buf, self._buf = self._buf, b''
                return buf
            return await self._read()

        while len(self._buf) < size:
            self._buf += (await self._read(size - len(self._buf)))
        _buf, self._buf = self._buf[:size], self._buf[size:]
        return _buf

    async def handle(self, client_reader, client_writer):
        try:
            await self._handle(client_reader, client_writer)
        except Exception as err:
            self.logger.error('HXsocksHandler.handle')
            self.logger.error(repr(err))
            self.logger.error(traceback.format_exc())
        client_writer.close()

    async def _handle(self, client_reader, client_writer):
        self.client_address = client_writer.get_extra_info('peername')[0]
        self.client_reader = client_reader
        self.logger.debug('incoming connection %s', self.client_address)

        # read iv
        try:
            fut = self.client_reader.readexactly(self.encryptor._iv_len)
            iv_ = await asyncio.wait_for(fut, timeout=10)
            self.encryptor.decrypt(iv_)
        except IVError:
            self.logger.error('iv reused, %s', self.client_address)
            await self.play_dead()
            return
        except (asyncio.TimeoutError, asyncio.IncompleteReadError,
                ConnectionResetError):
            self.logger.warning('iv read failed, %s', self.client_address)
            return

        # read cmd
        try:
            fut = self.read(1)
            cmd = await asyncio.wait_for(fut, timeout=10)
        except asyncio.TimeoutError:
            self.logger.debug('read cmd timed out. %s', self.client_address)
            return
        except (ConnectionResetError, asyncio.IncompleteReadError):
            self.logger.debug('read cmd reset. %s', self.client_address)
            return
        except InvalidTag:
            self.logger.error('InvalidTag while read cmd. %s',
                              self.client_address)
            await self.play_dead()
            return
        cmd = cmd[0]
        self.logger.debug('cmd: %s %s', cmd, self.client_address)

        if cmd in (1, 3, 4):
            # A shadowsocks request
            result = await self.handle_ss(client_writer, addr_type=cmd)
            if result:
                await self.play_dead()
            return
        if cmd == 20:  # hxsocks2 client key exchange
            rint = random.randint(64, 2048)
            req_len = await self.read(2)
            req_len, = struct.unpack('>H', req_len)
            data = await self.read(req_len)
            data = io.BytesIO(data)
            ts = int(time.time()) // 30

            pklen = data.read(1)[0]
            client_pkey = data.read(pklen)
            client_auth = data.read(32)

            def _send(data):
                if self.encryptor._encryptor:
                    data = struct.pack('>H', len(data)) + data
                    ct = self.encryptor.encrypt(data)
                    client_writer.write(struct.pack('>H', len(ct)) + ct)
                else:
                    data = struct.pack('>H', len(data)) + data
                    client_writer.write(self.encryptor.encrypt(data))

            def auth():
                for _ts in [ts, ts - 1, ts + 1]:
                    for user, passwd in self.user_mgr.iter_user():
                        h = hmac.new(
                            passwd.encode(),
                            struct.pack('>I', _ts) + client_pkey +
                            user.encode(), hashlib.sha256).digest()
                        if compare_digest(h, client_auth):
                            return user
                return None

            client = auth()
            if not client:
                self.logger.error('user not found. %s', self.client_address)
                await self.play_dead()
                return
            try:
                pkey, passwd = self.user_mgr.key_xchange(
                    client, client_pkey, self.encryptor._key_len)
                self.logger.info('new key exchange. client: %s %s', client,
                                 self.client_address)
                h = hmac.new(passwd.encode(),
                             client_pkey + pkey + client.encode(),
                             hashlib.sha256).digest()
                scert = self.user_mgr.SERVER_CERT.get_pub_key()
                signature = self.user_mgr.SERVER_CERT.sign(h, DEFAULT_HASH)
                data = bytes(
                    (0, len(pkey), len(scert), len(signature)
                     )) + pkey + h + scert + signature + os.urandom(rint)
                _send(data)

                client_pkey = hashlib.md5(client_pkey).digest()
                conn = Hxs2Connection(
                    client_reader, client_writer, client,
                    self.user_mgr.get_skey_by_pubkey(client_pkey),
                    self.server.method, self.server.proxy, self.user_mgr,
                    self.address[1], self.logger)
                await conn.wait_close()
                self.user_mgr.del_key(client_pkey)
                return
            except ValueError as err:
                self.logger.error('key exchange failed. %s %s',
                                  self.client_address, err)
                await self.play_dead()
                return

        # TODO: security log
        self.logger.error('bad cmd: %s, %s', cmd, self.client_address)
        await self.play_dead()
        return

    async def play_dead(self, timeout=1):
        count = random.randint(6, 15)
        for _ in range(count):
            fut = self.client_reader.read(self.bufsize)
            try:
                await asyncio.wait_for(fut, timeout)
            except (asyncio.TimeoutError, ConnectionResetError):
                return

    async def handle_ss(self, client_writer, addr_type):
        # if error, return 1
        # get header...
        try:
            assert addr_type in (1, 3, 4)
            if addr_type == 1:
                addr = await self.read(4)
                addr = socket.inet_ntoa(addr)
            elif addr_type == 3:
                data = await self.read(1)
                addr = await self.read(data[0])
                addr = addr.decode('ascii')
            else:
                data = await self.read(16)
                addr = socket.inet_ntop(socket.AF_INET6, data)
            port = await self.read(2)
            port, = struct.unpack('>H', port)
        except Exception as err:
            self.logger.error('error on read ss header: %s %s', err,
                              self.client_address)
            self.logger.error(traceback.format_exc())
            return 1

        # access control
        try:
            self.user_mgr.user_access_ctrl(self.address[1], addr,
                                           self.client_address)
        except ValueError as err:
            self.logger.error('access denied! %s:%s, %s %s', addr, port, err)
            return

        # create connection
        self.logger.info('connect to %s:%d %r', addr, port,
                         self.client_address)

        try:
            remote_reader, remote_writer = await open_connection(
                addr, port, self.server.proxy)
        except Exception as err:
            self.logger.error('connect to %s:%s failed! %r', addr, port, err)
            return

        # forward
        context = ForwardContext()

        tasks = [
            self.ss_forward_a(remote_writer, context),
            self.ss_forward_b(remote_reader, client_writer,
                              self.encryptor.encrypt, context),
        ]
        try:
            await asyncio.wait(tasks)
        except Exception as err:
            self.logger.error(repr(err))
            self.logger.error(traceback.format_exc())
        remote_writer.close()

        # access log
        traffic = (context.traffic_from_client, context.traffic_from_remote)
        self.user_mgr.user_access_log(self.address[1], addr, traffic,
                                      self.client_address)

    async def ss_forward_a(self, write_to, context, timeout=60):
        # data from ss client, decrypt, sent to remote
        while True:
            try:
                fut = self.read()
                data = await asyncio.wait_for(fut, timeout=5)
                context.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time(
                ) - context.last_active > timeout or context.remote_eof:
                    data = b''
                else:
                    continue
            except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag,
                    ConnectionResetError, OSError):
                data = b''

            if not data:
                break
            context.traffic_from_client += len(data)
            try:
                write_to.write(data)
                await write_to.drain()
            except ConnectionResetError:
                context.local_eof = True
                return
        context.local_eof = True
        try:
            write_to.write_eof()
        except (ConnectionResetError, OSError):
            pass

    async def ss_forward_b(self,
                           read_from,
                           write_to,
                           cipher,
                           context,
                           timeout=60):
        # data from remote, encrypt, sent to client
        while True:
            try:
                fut = read_from.read(self.bufsize)
                data = await asyncio.wait_for(fut, timeout=5)
                context.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time(
                ) - context.last_active > timeout or context.local_eof:
                    data = b''
                else:
                    continue
            except (ConnectionResetError, OSError):
                data = b''

            if not data:
                break

            context.traffic_from_remote += len(data)

            data = cipher(data)
            try:
                write_to.write(data)
                await write_to.drain()
            except ConnectionResetError:
                context.remote_eof = True
                return
        context.remote_eof = True
Example #11
0
class SSConn:
    bufsize = 65535
    tcp_timeout = 600

    def __init__(self, proxy, ):
        self.logger = logging.getLogger('ss')
        self.proxy = proxy
        ssmethod, sspassword = self.proxy.username, self.proxy.password
        if sspassword is None:
            ssmethod, sspassword = base64.b64decode(ssmethod).decode().split(':', 1)
        ssmethod = ssmethod.lower()

        self._address = None
        self._port = 0
        self.client_reader = None
        self.client_writer = None
        self.remote_reader = None
        self.remote_writer = None
        self.task = None

        self.aead = is_aead(ssmethod)
        self.crypto = Encryptor(sspassword, ssmethod)
        self.connected = False
        self.last_active = time.time()
        # if eof recieved
        self.remote_eof = False
        self.client_eof = False
        self.data_recved = False
        self._buf = b''

    async def connect(self, addr, port, timeout, limit, tcp_nodelay):
        self._address = addr
        self._port = port

        from .connection import open_connection
        self.remote_reader, self.remote_writer, _ = await open_connection(
            self.proxy.hostname,
            self.proxy.port,
            proxy=self.proxy.get_via(),
            timeout=timeout,
            tunnel=True,
            limit=131072,
            tcp_nodelay=tcp_nodelay)
        # self.remote_writer.transport.set_write_buffer_limits(262144)

        # create socket_pair
        sock_a, sock_b = socket.socketpair()
        if sys.platform == 'win32':
            sock_a.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            sock_a.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.client_reader, self.client_writer = await asyncio.open_connection(sock=sock_b)
        self.client_writer.transport.set_write_buffer_limits(262144)

        # start forward
        self.task = asyncio.ensure_future(self.forward())

        # return reader, writer
        reader, writer = await asyncio.open_connection(sock=sock_a, limit=limit)
        return reader, writer

    async def forward(self):

        tasks = [asyncio.create_task(self.forward_from_client()),
                 asyncio.create_task(self.forward_from_remote()),
                 ]
        await asyncio.wait(tasks)
        for writer in (self.remote_writer, self.client_writer):
            try:
                writer.close()
                await writer.wait_closed()
            except ConnectionError:
                pass

    async def forward_from_client(self):
        # read from client, encrypt, sent to server
        while True:
            fut = self.client_reader.read(self.bufsize)
            try:
                data = await asyncio.wait_for(fut, timeout=6)
                self.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time() - self.last_active > self.tcp_timeout or self.remote_eof:
                    data = b''
                else:
                    continue
            except OSError:
                data = b''

            if not data:
                break
            if not self.connected:
                header = b''.join([chr(3).encode(),
                                   chr(len(self._address)).encode('latin1'),
                                   self._address.encode(),
                                   struct.pack(b">H", self._port)])
                data = header + data
                self.connected = True

            self.remote_writer.write(self.crypto.encrypt(data))
            try:
                await self.remote_writer.drain()
            except ConnectionError:
                break
        self.client_eof = True
        try:
            self.remote_writer.write_eof()
        except ConnectionError:
            pass

    async def _read(self):
        if self.aead:
            fut = self.remote_reader.readexactly(18)
            _len = await asyncio.wait_for(fut, timeout=6)
            if not _len:
                return b''
            _len = self.crypto.decrypt(_len)
            _len, = struct.unpack("!H", _len)
            fut = self.remote_reader.readexactly(_len + 16)
            try:
                ct = await asyncio.wait_for(fut, timeout=2)
            except asyncio.TimeoutError as err:
                raise IncompleteChunk() from err
            if not ct:
                return b''
        else:
            ct = await self.remote_reader.read(self.bufsize)
        return self.crypto.decrypt(ct)

    async def forward_from_remote(self):
        # read from remote, decrypt, sent to client
        try:
            fut = self.remote_reader.readexactly(self.crypto.iv_len)
            iv_ = await asyncio.wait_for(fut, timeout=12)
            self.crypto.decrypt(iv_)
        except (asyncio.TimeoutError, asyncio.IncompleteReadError):
            self.remote_eof = True
            try:
                self.client_writer.write_eof()
            except ConnectionError:
                pass
            return

        while True:
            try:
                data = await self._read()
                self.last_active = time.time()
                self.data_recved = True
            except asyncio.TimeoutError:
                if time.time() - self.last_active > self.tcp_timeout or self.client_eof:
                    data = b''
                else:
                    continue
            except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag, IncompleteChunk):
                data = b''

            if not data:
                break
            try:
                self.client_writer.write(data)
                await self.client_writer.drain()
            except ConnectionError:
                break
        self.remote_eof = True
        try:
            self.client_writer.write_eof()
        except ConnectionError:
            pass
Example #12
0
 def __init__(self, server):
     self.server = server
     self.logger = server.logger
     self.encryptor = Encryptor(self.server.PSK, self.server.method)
     self._buf = b''
Example #13
0
class HXsocksHandler:
    bufsize = 8192

    def __init__(self, server):
        self.server = server
        self.logger = server.logger
        self.encryptor = Encryptor(self.server.PSK, self.server.method)
        self._buf = b''

    async def _read(self, size=None):
        if self.server.aead:
            _len = await self.client_reader.readexactly(18)
            if not _len:
                return b''
            _len = self.encryptor.decrypt(_len)
            _len, = struct.unpack("!H", _len)
            ct = await self.client_reader.readexactly(_len + 16)
            if not ct:
                return b''
        else:
            size = size or self.bufsize
            ct = await self.client_reader.read(size)
        return self.encryptor.decrypt(ct)

    async def read(self, size=None):
        # compatible with shadowsocks aead
        if not size:
            if self._buf:
                buf, self._buf = self._buf, b''
                return buf
            else:
                return await self._read()
        else:
            while len(self._buf) < size:
                self._buf += (await self._read(size - len(self._buf)))
            _buf, self._buf = self._buf[:size], self._buf[size:]
            return _buf

    async def handle(self, client_reader, client_writer):
        try:
            await self._handle(client_reader, client_writer)
        except Exception as e:
            self.logger.error(repr(e))
            self.logger.error(traceback.format_exc())
        client_writer.close()

    async def _handle(self, client_reader, client_writer):
        self.client_address = client_writer.get_extra_info('peername')
        self.client_reader = client_reader
        self.logger.debug('incoming connection {}'.format(self.client_address))

        KM = self.server.kmgr

        try:
            fut = self.client_reader.readexactly(self.encryptor._iv_len)
            iv = await asyncio.wait_for(fut, timeout=10)
            self.encryptor.decrypt(iv)
        except IVError:
            self.logger.error('iv reused, {}'.format(self.client_address))
            await self.play_dead()
            return
        except (asyncio.TimeoutError, asyncio.IncompleteReadError, ConnectionResetError):
            self.logger.warning('iv read failed, {}'.format(self.client_address))
            return

        req_count = 0

        while True:
            if req_count:
                # Not shadowsocks request
                self.logger.debug('excepting next request: {}'.format(self.client_address))
                fut = client_reader.readexactly(2)
                try:
                    await asyncio.wait_for(fut, timeout=120)
                except (OSError, ConnectionResetError, asyncio.IncompleteReadError, asyncio.TimeoutError) as e:
                    self.logger.debug('closed: {} {}'.format(e, self.client_address))
                    return

            try:
                fut = self.read(1)
                cmd = await asyncio.wait_for(fut, timeout=10)
            except asyncio.TimeoutError:
                self.logger.debug('read cmd timed out. {}'.format(self.client_address))
                return
            except (ConnectionResetError, asyncio.IncompleteReadError):
                self.logger.debug('read cmd reset. {}'.format(self.client_address))
                return
            except InvalidTag:
                self.logger.error('InvalidTag while read cmd. {}'.format(self.client_address))
                await self.play_dead()
                return
            cmd = cmd[0]
            self.logger.debug('cmd: {} {}'.format(cmd, self.client_address))

            if cmd in (1, 3, 4):
                # A shadowsocks request
                result = await self.handle_ss(client_reader, client_writer, addr_type=cmd)
                if result:
                    await self.play_dead()
                return
            elif cmd in (10, 20):  # hxsocks / hxsocks2 client key exchange
                req_count += 1
                rint = random.randint(64, 2048)
                req_len = await self.read(2)
                req_len, = struct.unpack('>H', req_len)
                data = await self.read(req_len)
                data = io.BytesIO(data)
                ts = int(time.time()) // 30

                pklen = data.read(1)[0]
                client_pkey = data.read(pklen)
                client_auth = data.read(32)

                def _send(data):
                    if self.encryptor._encryptor:
                        data = struct.pack('>H', len(data)) + data
                        ct = self.encryptor.encrypt(data)
                        client_writer.write(struct.pack('>H', len(ct)) + ct)
                        return
                    data = struct.pack('>H', len(data)) + data
                    client_writer.write(self.encryptor.encrypt(data))

                def auth():
                    for _ts in [ts, ts - 1, ts + 1]:
                        for user, passwd in KM.iter_user():
                            h = hmac.new(passwd.encode(), struct.pack('>I', _ts) + client_pkey + user.encode(), hashlib.sha256).digest()
                            if compare_digest(h, client_auth):
                                return user

                client = auth()
                if not client:
                    self.logger.error('user not found. {}'.format(self.client_address))
                    await self.play_dead()
                    return
                pkey, passwd = KM.key_xchange(client, client_pkey, self.encryptor._key_len)
                if pkey:
                    self.logger.info('new key exchange. client: {} {}'.format(client, self.client_address))
                    h = hmac.new(passwd.encode(), client_pkey + pkey + client.encode(), hashlib.sha256).digest()
                    scert = KM.SERVER_CERT.get_pub_key()
                    signature = KM.SERVER_CERT.sign(h, DEFAULT_HASH)
                    data = bytes((0, len(pkey), len(scert), len(signature))) + pkey + h + scert + signature + os.urandom(rint)
                    _send(data)
                    if cmd == 20:
                        client_pkey = hashlib.md5(client_pkey).digest()
                        conn = hxs2_connection(client_reader,
                                               client_writer,
                                               KM.get_skey_by_pubkey(client_pkey),
                                               self.server.method,
                                               self.server.proxy,
                                               self.logger)
                        await conn.wait_close()
                        return
                    continue
                else:
                    self.logger.error('Private_key already registered. client: {} {}'.format(client, self.client_address))
                    await self.play_dead()
                    return
            elif cmd == 11:  # a connect request
                req_count += 1
                client_pkey = await self.read(16)
                rint = random.randint(64, 2048)

                def _send(code, cipher):
                    if code == 1:
                        client_writer.write(struct.pack('>H', rint) + os.urandom(rint))
                    else:
                        ct = cipher.encrypt(bytes((code, )) + os.urandom(rint))
                        client_writer.write(struct.pack('>H', len(ct)) + ct)

                if KM.check_key(client_pkey):
                    self.logger.error('client key not exist or expired. {}'.format(self.client_address))
                    ctlen = await self.read(2)
                    ctlen, = struct.unpack('>H', ctlen)
                    await self.read(ctlen)
                    _send(1, None)
                    continue

                user = KM.get_user_by_pubkey(client_pkey)
                cipher = AEncryptor(KM.get_skey_by_pubkey(client_pkey), self.server.method, CTX)

                ctlen = await self.read(2)
                ctlen, = struct.unpack('>H', ctlen)

                ct = await self.read(ctlen)
                try:
                    data = cipher.decrypt(ct)
                except InvalidTag:
                    self.logger.error('hxs connect req InvalidTag. {} {}'.format(user, self.client_address))
                    # await self.play_dead()
                    return
                buf = io.BytesIO(data)
                ts = buf.read(4)
                if abs(struct.unpack('>I', ts)[0] - time.time()) > 600:
                    self.logger.error('bad timestamp, possible replay attrack. {} {}'.format(user, self.client_address))
                    # KM.del_key(client_pkey)
                    # _send(1, None)
                    await self.play_dead()
                    return

                host_len = buf.read(1)[0]
                addr = buf.read(host_len).decode('ascii')
                port, = struct.unpack('>H', buf.read(2))

                self.logger.info('connecting to {}:{} via {}, {} {} {}'.format(addr, port, self.server.proxy, user, req_count, self.client_address))

                try:
                    remote_reader, remote_writer = await open_connection(addr, port, self.server.proxy)
                    _send(0, cipher)
                except Exception:
                    self.logger.error('connect to {}:{} failed!'.format(addr, port))
                    _send(2, cipher)
                    continue

                context = ForwardContext()
                tasks = [self.hxs_forward_from_remote(remote_reader, client_writer, cipher, context),
                         self.hxs_forward_from_client(client_reader, client_writer, remote_writer, cipher, context),
                         ]
                await asyncio.wait(tasks)
                remote_writer.close()
                if context.readable or context.writeable:
                    return
                continue
            elif cmd == 12:  # get public key
                req_len = await self.read(2)
                req_len, = struct.unpack('>H', req_len)
                data = await self.read(req_len)
                # drop data
                # return public key with padding
                rint = random.randint(64, 2048)
                scert = KM.SERVER_CERT.get_pub_key()
                data = struct.pack('>H', len(scert)) + scert + os.urandom(rint)
                data = struct.pack('>H', len(data)) + data
                # the first response, just encrypt and sent
                client_writer.write(self.encryptor.encrypt(data))
                continue
            else:
                # TODO: security
                self.logger.error('bad cmd: %s, %s' % (cmd, self.client_address))
                await self.play_dead()
                return

    async def play_dead(self, timeout=1):
        for _ in range(10):
            fut = self.client_reader.read(self.bufsize)
            try:
                await asyncio.wait_for(fut, timeout=1)
            except (asyncio.TimeoutError, ConnectionResetError):
                return

    async def hxs_forward_from_remote(self, remote_reader, client_writer, cipher, context, timeout=120):
        # read from remote_reader, write to client_writer
        total_send = 0
        while not context.remote_eof:
            try:
                fut = remote_reader.read(self.bufsize)
                data = await asyncio.wait_for(fut, timeout=5)
                context.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time() - context.last_active > timeout or context.local_eof:
                    data = b''
                else:
                    continue
            except (ConnectionResetError, OSError):
                data = b''

            if not data:
                # timeout or remote closed...
                context.remote_eof = True
                if total_send < 8196 and random.random() < 0.5:
                    # sent fake chunk before close
                    _data = bytes((2, )) + b'\x00' * random.randint(1024, 8196)
                    ct = cipher.encrypt(_data)
                    _data = struct.pack('>H', len(ct)) + ct
                    client_writer.write(_data)
            # send data / close link
            total_send += len(data)
            padding_len = random.randint(8, 255)
            data = bytes((padding_len, )) + data + b'\x00' * padding_len
            ct = cipher.encrypt(data)
            data = struct.pack('>H', len(ct)) + ct

            try:
                client_writer.write(data)
                await client_writer.drain()
            except ConnectionResetError:
                context.local_eof = True
                return
        context.writeable = False

    async def hxs_forward_from_client(self, client_reader, client_writer, remote_writer, cipher, context, timeout=200):
        # data from hxs client
        remote_writable = True
        while not context.local_eof:
            try:
                fut = client_reader.readexactly(2)
                ct_len = await asyncio.wait_for(fut, timeout=10)
                # client is supposed to close hxs link
                ct_len, = struct.unpack('>H', ct_len)
            except asyncio.TimeoutError:
                if time.time() - context.last_active > timeout or context.remote_eof:
                    # timeout, sent remote eof...
                    remote_writer.write_eof()
                    remote_writable = False
                    break
                else:
                    continue
            except (ConnectionResetError, OSError, asyncio.IncompleteReadError):
                context.local_eof = True
                break

            try:
                fut = client_reader.readexactly(ct_len)
                ct = await asyncio.wait_for(fut, timeout=5)
                data = cipher.decrypt(ct)
                pad_len = data[0]

                if 0 < pad_len < 8:
                    # fake chunk, drop
                    if pad_len == 1 and context.writeable:
                        _data = bytes((2, )) + b'\x00' * random.randint(1024, 8196)
                        ct = cipher.encrypt(_data)
                        _data = struct.pack('>H', len(ct)) + ct
                        client_writer.write(_data)
                    continue
            except (asyncio.TimeoutError, BufEmptyError, asyncio.IncompleteReadError, ValueError, ConnectionResetError):
                context.local_eof = True
                remote_writer.write_eof()
                return

            data = data[1:0 - pad_len] if pad_len else data[1:]
            if data and remote_writable:
                context.last_active = time.time()
                remote_writer.write(data)
                await remote_writer.drain()
                # ConnectionResetError
            else:
                # client closed, gracefully
                context.readable = False
                try:
                    remote_writer.write_eof()
                except ConnectionResetError:
                    pass
                break
        context.local_eof = True

    async def handle_ss(self, client_reader, client_writer, addr_type):
        # if error, return 1
        # get header...
        try:
            assert addr_type in (1, 3, 4)
            if addr_type & 15 == 1:
                addr = await self.read(4)
                addr = socket.inet_ntoa(addr)
            elif addr_type & 15 == 3:
                data = await self.read(1)
                addr = await self.read(data[0])
                addr = addr.decode('ascii')
            else:
                data = await self.read(16)
                addr = socket.inet_ntop(socket.AF_INET6, data)
            port = await self.read(2)
            port, = struct.unpack('>H', port)
        except Exception as e:
            self.logger.error('error on read ss header: {} {}'.format(e, self.client_address))
            self.logger.error(traceback.format_exc())
            return 1

        self.logger.info('connect to {}:{} {!r} {!r}'.format(addr, port, self.client_address, self.server.proxy))

        try:
            remote_reader, remote_writer = await open_connection(addr, port, self.server.proxy)
        except Exception as e:
            self.logger.error('connect to {}:{} failed! {!r}'.format(addr, port, e))
            return

        context = ForwardContext()

        tasks = [self.ss_forward_A(client_reader, remote_writer, self.encryptor.decrypt, context),
                 self.ss_forward_B(remote_reader, client_writer, self.encryptor.encrypt, context),
                 ]
        try:
            await asyncio.wait(tasks)
        except Exception as e:
            self.logger.error(repr(e))
            self.logger.error(traceback.format_exc())
        remote_writer.close()

    async def ss_forward_A(self, read_from, write_to, cipher, context, timeout=60):
        # data from ss client
        while True:
            try:
                fut = self.read()
                data = await asyncio.wait_for(fut, timeout=5)
                context.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time() - context.last_active > timeout or context.remote_eof:
                    data = b''
                else:
                    continue
            except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag, ConnectionResetError, OSError):
                data = b''

            if not data:
                break
            try:
                write_to.write(data)
                await write_to.drain()
            except ConnectionResetError:
                context.local_eof = True
                return
        context.local_eof = True
        try:
            write_to.write_eof()
        except (ConnectionResetError, OSError):
            pass

    async def ss_forward_B(self, read_from, write_to, cipher, context, timeout=60):
        # data from remote
        while True:
            try:
                fut = read_from.read(self.bufsize)
                data = await asyncio.wait_for(fut, timeout=5)
                context.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time() - context.last_active > timeout or context.local_eof:
                    data = b''
                else:
                    continue
            except (ConnectionResetError, OSError):
                data = b''

            if not data:
                break
            data = cipher(data)
            try:
                write_to.write(data)
                await write_to.drain()
            except ConnectionResetError:
                context.remote_eof = True
                return
        context.remote_eof = True
Example #14
0
class SSConn:
    bufsize = 32768

    def __init__(self, proxy, sock_b):
        self.logger = logging.getLogger('ss')
        self.proxy = proxy
        self.sock_b = sock_b
        ssmethod, sspassword = self.proxy.username, self.proxy.password
        if sspassword is None:
            import base64
            ssmethod, sspassword = base64.b64decode(ssmethod).decode().split(
                ':', 1)
        ssmethod = ssmethod.lower()

        self._address = None
        self._port = 0
        self.client_reader = None
        self.client_writer = None
        self.remote_reader = None
        self.remote_writer = None

        self.aead = is_aead(ssmethod)
        self.crypto = Encryptor(sspassword, ssmethod)
        self.connected = False
        self.last_active = time.time()
        # if eof recieved
        self.remote_eof = False
        self.client_eof = False
        self.data_recved = False
        self._buf = b''

    async def connect(self, addr, port, timeout):
        self._address = addr
        self._port = port
        self.client_reader, self.client_writer = await asyncio.open_connection(
            sock=self.sock_b)
        self.client_writer.transport.set_write_buffer_limits(0, 0)

        from .connection import open_connection
        self.remote_reader, self.remote_writer, _ = await open_connection(
            self.proxy.hostname,
            self.proxy.port,
            proxy=self.proxy.get_via(),
            timeout=timeout,
            tunnel=True)

    async def forward(self):

        tasks = [
            self.forward_from_client(),
            self.forward_from_remote(),
        ]
        try:
            await asyncio.wait(tasks)
        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.logger.error('SSConn.forward')
            self.logger.error(repr(e))
            self.logger.error(traceback.format_exc())
        for writer in (self.remote_writer, self.client_writer):
            try:
                writer.close()
            except ConnectionResetError:
                pass

    async def forward_from_client(self):
        # read from client, encrypt, sent to server
        while True:
            intv = 5 if self.data_recved else 1
            fut = self.client_reader.read(self.bufsize)
            try:
                data = await asyncio.wait_for(fut, timeout=intv)
                self.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time() - self.last_active > 60 or self.remote_eof:
                    data = b''
                else:
                    continue
            except (ConnectionResetError, OSError, AttributeError):
                data = b''

            if not data:
                break
            if not self.connected:
                header = b''.join([
                    chr(3).encode(),
                    chr(len(self._address)).encode('latin1'),
                    self._address.encode(),
                    struct.pack(b">H", self._port)
                ])
                data = header + data
                self.connected = True

            self.remote_writer.write(self.crypto.encrypt(data))
        self.client_eof = True
        try:
            self.remote_writer.write_eof()
        except (ConnectionResetError, OSError, AttributeError):
            pass

    async def _read(self, size=None):
        if self.aead:
            _len = await self.remote_reader.readexactly(18)
            if not _len:
                return b''
            _len = self.crypto.decrypt(_len)
            _len, = struct.unpack("!H", _len)
            fut = self.remote_reader.readexactly(_len + 16)
            try:
                ct = await asyncio.wait_for(fut, timeout=1)
            except asyncio.TimeoutError:
                raise IncompleteChunk()
            if not ct:
                return b''
        else:
            size = size or self.bufsize
            ct = await self.remote_reader.read(size)
        return self.crypto.decrypt(ct)

    async def forward_from_remote(self):
        # read from remote, decrypt, sent to client
        try:
            fut = self.remote_reader.readexactly(self.crypto._iv_len)
            iv = await asyncio.wait_for(fut, timeout=60)
            self.crypto.decrypt(iv)
        except (asyncio.TimeoutError, asyncio.IncompleteReadError):
            self.remote_eof = True
            try:
                self.client_writer.write_eof()
            except (ConnectionResetError, OSError, AttributeError):
                pass
            return

        while True:
            try:
                fut = self._read()
                data = await asyncio.wait_for(fut, timeout=5)
                self.last_active = time.time()
                self.data_recved = True
            except asyncio.TimeoutError:
                if time.time() - self.last_active > 60 or self.client_eof:
                    data = b''
                else:
                    continue
            except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag,
                    IncompleteChunk):
                data = b''

            if not data:
                break
            try:
                self.client_writer.write(data)
                await self.client_writer.drain()
            except ConnectionResetError:
                break
        self.remote_eof = True
        try:
            self.client_writer.write_eof()
        except (ConnectionResetError, OSError, AttributeError):
            pass
Example #15
0
 def __init__(self, server):
     self.server = server
     self.logger = server.logger
     self.encryptor = Encryptor(self.server.PSK, self.server.method)
     self._buf = b''
Example #16
0
class ShadowsocksHandler:
    bufsize = 8192

    def __init__(self, server):
        self.server = server
        self.logger = server.logger
        self.encryptor = Encryptor(self.server.PSK, self.server.method)
        self._buf = b''

    async def _read(self, size=None):
        if self.server.aead:
            _len = await self.client_reader.readexactly(18)
            if not _len:
                return b''
            _len = self.encryptor.decrypt(_len)
            _len, = struct.unpack("!H", _len)
            ct = await self.client_reader.readexactly(_len + 16)
            if not ct:
                return b''
        else:
            size = size or self.bufsize
            ct = await self.client_reader.read(size)
        return self.encryptor.decrypt(ct)

    async def read(self, size=None):
        # compatible with shadowsocks aead
        if not size:
            if self._buf:
                buf, self._buf = self._buf, b''
                return buf
            else:
                return await self._read()
        else:
            while len(self._buf) < size:
                self._buf += (await self._read(size - len(self._buf)))
            _buf, self._buf = self._buf[:size], self._buf[size:]
            return _buf

    async def handle(self, client_reader, client_writer):
        try:
            await self._handle(client_reader, client_writer)
        except Exception as e:
            self.logger.error(repr(e))
            self.logger.error(traceback.format_exc())
        client_writer.close()

    async def _handle(self, client_reader, client_writer):
        self.client_address = client_writer.get_extra_info('peername')
        self.client_reader = client_reader
        self.logger.debug('incoming connection {}'.format(self.client_address))

        try:
            fut = self.client_reader.readexactly(self.encryptor._iv_len)
            iv = await asyncio.wait_for(fut, timeout=10)
            self.encryptor.decrypt(iv)
        except IVError:
            self.logger.error('iv reused, {}'.format(self.client_address))
            await self.play_dead()
            return
        except (asyncio.TimeoutError, asyncio.IncompleteReadError):
            self.logger.warning('iv read failed, {}'.format(
                self.client_address))
            return

        try:
            fut = self.read(1)
            cmd = await asyncio.wait_for(fut, timeout=10)
        except asyncio.TimeoutError:
            self.logger.warning('read cmd timed out. {}'.format(
                self.client_address))
            return
        except (ConnectionResetError, asyncio.IncompleteReadError):
            return
        except InvalidTag:
            self.logger.error('InvalidTag while read cmd. {}'.format(
                self.client_address))
            await self.play_dead()
            return
        cmd = cmd[0]
        self.logger.debug('cmd: {} {}'.format(cmd, self.client_address))

        if cmd in (1, 3, 4):
            # A shadowsocks request
            result = await self.handle_ss(client_reader,
                                          client_writer,
                                          addr_type=cmd)
            if result:
                await self.play_dead()
            return
        else:
            # TODO: security
            self.logger.error('bad cmd: %s, %s' % (cmd, self.client_address))
            await self.play_dead()
            return

    async def play_dead(self, timeout=1):
        for _ in range(10):
            fut = self.client_reader.read(self.bufsize)
            try:
                await asyncio.wait_for(fut, timeout=1)
            except (asyncio.TimeoutError, ConnectionResetError):
                return

    async def open_connection(self, addr, port, proxy):
        # do security check here
        data = await self.request_is_loopback(addr)
        if data:
            raise ValueError('connect to localhost denied! {}'.format(
                self.client_address))

        # create connection
        if proxy:
            fut = asyncio.open_connection(proxy[0], proxy[1])
            remote_reader, remote_writer = await asyncio.wait_for(fut,
                                                                  timeout=5)
            s = 'CONNECT {0}:{1} HTTP/1.1\r\nHost: {0}:{1}\r\n\r\n'.format(
                addr, port)
            remote_writer.write(s.encode())
            data = await remote_reader.readuntil(b'\r\n\r\n')
            if b'200' not in data:
                raise IOError(0, 'create tunnel via %s failed!' % proxy)
            return remote_reader, remote_writer

        fut = asyncio.open_connection(addr, port)
        remote_reader, remote_writer = await asyncio.wait_for(fut, timeout=5)
        return remote_reader, remote_writer

    async def handle_ss(self, client_reader, client_writer, addr_type):
        # if error, return 1
        # get header...
        try:
            assert addr_type in (1, 3, 4)
            if addr_type & 15 == 1:
                addr = await self.read(4)
                addr = socket.inet_ntoa(addr)
            elif addr_type & 15 == 3:
                data = await self.read(1)
                addr = await self.read(data[0])
                addr = addr.decode('ascii')
            else:
                data = await self.read(16)
                addr = socket.inet_ntop(socket.AF_INET6, data)
            port = await self.read(2)
            port, = struct.unpack('>H', port)
        except Exception as e:
            self.logger.error('error on read ss header: {} {}'.format(
                e, self.client_address))
            self.logger.error(traceback.format_exc())
            return 1

        self.logger.info('connect to {}:{} {!r} {!r}'.format(
            addr, port, self.client_address, self.server.proxy))

        try:
            remote_reader, remote_writer = await self.open_connection(
                addr, port, self.server.proxy)
        except Exception as e:
            self.logger.error('connect to {}:{} failed! {!r}'.format(
                addr, port, e))
            return

        context = ForwardContext()

        tasks = [
            self.ss_forward_A(client_reader, remote_writer,
                              self.encryptor.decrypt, context),
            self.ss_forward_B(remote_reader, client_writer,
                              self.encryptor.encrypt, context),
        ]
        try:
            await asyncio.wait(tasks)
        except Exception as e:
            self.logger.error(repr(e))
            self.logger.error(traceback.format_exc())
        remote_writer.close()

    async def ss_forward_A(self,
                           read_from,
                           write_to,
                           cipher,
                           context,
                           timeout=60):
        # data from ss client
        while True:
            try:
                fut = self.read()
                data = await asyncio.wait_for(fut, timeout=5)
                context.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time(
                ) - context.last_active > timeout or context.remote_eof:
                    data = b''
                else:
                    continue
            except (BufEmptyError, asyncio.IncompleteReadError, InvalidTag,
                    ConnectionResetError, OSError):
                data = b''

            if not data:
                break
            try:
                write_to.write(data)
                await write_to.drain()
            except ConnectionResetError:
                context.local_eof = True
                return
        context.local_eof = True
        try:
            write_to.write_eof()
        except (ConnectionResetError, OSError):
            pass

    async def ss_forward_B(self,
                           read_from,
                           write_to,
                           cipher,
                           context,
                           timeout=60):
        # data from remote
        while True:
            try:
                fut = read_from.read(self.bufsize)
                data = await asyncio.wait_for(fut, timeout=5)
                context.last_active = time.time()
            except asyncio.TimeoutError:
                if time.time(
                ) - context.last_active > timeout or context.local_eof:
                    data = b''
                else:
                    continue
            except (ConnectionResetError, OSError):
                data = b''

            if not data:
                break
            data = cipher(data)
            try:
                write_to.write(data)
                await write_to.drain()
            except ConnectionResetError:
                context.remote_eof = True
                return
        context.remote_eof = True
        # write_to.write_eof()

    async def get_ip_address(self, host):
        try:
            return ipaddress(host)
        except Exception:
            try:
                return ipaddress((await self.loop.getaddrinfo(host))[0][4][1])
            except Exception:
                return ipaddress('0.0.0.0')

    async def request_is_loopback(self, addr):
        try:
            ip = await self.get_ip_address(addr)
            self.logger.debug('requesting {}'.format(ip))
            if ip.is_loopback:
                return ip
        except Exception:
            pass