From f5c30c611530943441e3ee6fb22bcb5a9b6d7ccc Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 29 Aug 2018 00:04:58 +0500 Subject: [PATCH 1/6] secure only mode --- mtprotoproxy.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 1d83cb5..fcc8ecb 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -147,6 +147,8 @@ def debug_signal(signum, frame): PREFER_IPV6 = config.get("PREFER_IPV6", socket.has_ipv6) # disables tg->client trafic reencryption, faster but less secure FAST_MODE = config.get("FAST_MODE", True) +# doesn't allow to connect in not-secure mode +SECURE_ONLY = config.get("SECURE_ONLY", False) STATS_PRINT_PERIOD = config.get("STATS_PRINT_PERIOD", 600) PROXY_INFO_UPDATE_PERIOD = config.get("PROXY_INFO_UPDATE_PERIOD", 24*60*60) TO_CLT_BUFSIZE = config.get("TO_CLT_BUFSIZE", 16384) @@ -588,6 +590,9 @@ async def handle_handshake(reader, writer): if proto_tag not in (PROTO_TAG_ABRIDGED, PROTO_TAG_INTERMEDIATE, PROTO_TAG_SECURE): continue + if SECURE_ONLY and proto_tag != PROTO_TAG_SECURE: + continue + dc_idx = int.from_bytes(decrypted[DC_IDX_POS:DC_IDX_POS+2], "little", signed=True) reader = CryptoWrappedStreamReader(reader, decryptor) @@ -1097,13 +1102,14 @@ def print_tg_info(): for user, secret in sorted(USERS.items(), key=lambda x: x[0]): for ip in ip_addrs: - params = {"server": ip, "port": PORT, "secret": secret} - params_encodeded = urllib.parse.urlencode(params, safe=':') - print("{}: tg://proxy?{}".format(user, params_encodeded), flush=True) + if not SECURE_ONLY: + params = {"server": ip, "port": PORT, "secret": secret} + params_encodeded = urllib.parse.urlencode(params, safe=':') + print("{}: tg://proxy?{}".format(user, params_encodeded), flush=True) params = {"server": ip, "port": PORT, "secret": "dd" + secret} params_encodeded = urllib.parse.urlencode(params, safe=':') - print("{}: tg://proxy?{} (beta)".format(user, params_encodeded), flush=True) + print("{}: tg://proxy?{}".format(user, params_encodeded), flush=True) def loop_exception_handler(loop, context): From 298614b1f67f7fa548c4eb3682974cddb0addd1b Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Sun, 16 Sep 2018 12:50:41 +0500 Subject: [PATCH 2/6] add an ability to specify listen address --- mtprotoproxy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index fcc8ecb..82691f4 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -157,6 +157,8 @@ def debug_signal(signum, frame): CLIENT_HANDSHAKE_TIMEOUT = config.get("CLIENT_HANDSHAKE_TIMEOUT", 10) CLIENT_ACK_TIMEOUT = config.get("CLIENT_ACK_TIMEOUT", 5*60) TG_CONNECT_TIMEOUT = config.get("TG_CONNECT_TIMEOUT", 10) +LISTEN_ADDR_IPV4 = config.get("LISTEN_ADDR_IPV4", "0.0.0.0") +LISTEN_ADDR_IPV6 = config.get("LISTEN_ADDR_IPV6", "::") TG_DATACENTER_PORT = 443 @@ -1159,12 +1161,12 @@ def main(): reuse_port = hasattr(socket, "SO_REUSEPORT") - task_v4 = asyncio.start_server(handle_client_wrapper, '0.0.0.0', PORT, + task_v4 = asyncio.start_server(handle_client_wrapper, LISTEN_ADDR_IPV4, PORT, limit=TO_TG_BUFSIZE, reuse_port=reuse_port, loop=loop) server_v4 = loop.run_until_complete(task_v4) if socket.has_ipv6: - task_v6 = asyncio.start_server(handle_client_wrapper, '::', PORT, + task_v6 = asyncio.start_server(handle_client_wrapper, LISTEN_ADDR_IPV6, PORT, limit=TO_TG_BUFSIZE, reuse_port=reuse_port, loop=loop) server_v6 = loop.run_until_complete(task_v6) From 780dbc5866fac612f6c878137423e0422d168ec3 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Thu, 20 Sep 2018 04:03:32 +0500 Subject: [PATCH 3/6] document all advanced options --- mtprotoproxy.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 82691f4..2787d51 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -144,20 +144,31 @@ def debug_signal(signum, frame): AD_TAG = bytes.fromhex(config.get("AD_TAG", "")) # load advanced settings +# if IPv6 avaliable, use it by default PREFER_IPV6 = config.get("PREFER_IPV6", socket.has_ipv6) # disables tg->client trafic reencryption, faster but less secure FAST_MODE = config.get("FAST_MODE", True) # doesn't allow to connect in not-secure mode SECURE_ONLY = config.get("SECURE_ONLY", False) +# delay in seconds between stats printing STATS_PRINT_PERIOD = config.get("STATS_PRINT_PERIOD", 600) +# delay in seconds between middle proxy info updates PROXY_INFO_UPDATE_PERIOD = config.get("PROXY_INFO_UPDATE_PERIOD", 24*60*60) +# max socket buffer size to the client direction, the more the faster, but more RAM hungry TO_CLT_BUFSIZE = config.get("TO_CLT_BUFSIZE", 16384) +# max socket buffer size to the telegram servers direction TO_TG_BUFSIZE = config.get("TO_TG_BUFSIZE", 65536) +# keepalive period for clients in secs CLIENT_KEEPALIVE = config.get("CLIENT_KEEPALIVE", 10*60) +# drop client after this timeout if the handshake fail CLIENT_HANDSHAKE_TIMEOUT = config.get("CLIENT_HANDSHAKE_TIMEOUT", 10) +# if client doesn't confirm data for this number of seconds, it is dropped CLIENT_ACK_TIMEOUT = config.get("CLIENT_ACK_TIMEOUT", 5*60) +# telegram servers connect timeout in seconds TG_CONNECT_TIMEOUT = config.get("TG_CONNECT_TIMEOUT", 10) +# listen address for IPv4 LISTEN_ADDR_IPV4 = config.get("LISTEN_ADDR_IPV4", "0.0.0.0") +# listen address for IPv6 LISTEN_ADDR_IPV6 = config.get("LISTEN_ADDR_IPV6", "::") TG_DATACENTER_PORT = 443 From d5daf8bbdfb94971e4ea9a7cbbc857467c9f85c2 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 13 Nov 2018 01:11:24 +0500 Subject: [PATCH 4/6] add secure only mode example in config --- config.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config.py b/config.py index 23297f2..966aa10 100644 --- a/config.py +++ b/config.py @@ -8,3 +8,7 @@ # Tag for advertising, obtainable from @MTProxybot # AD_TAG = "3c09c680b76ee91a4c25ad51f742267d" + +# Uncommenting this do make a proxy harder to detect +# But it can be incompatible with old clients +# SECURE_ONLY = True From dd1d0a6262fe474fd6848b9bec0d79832c33868f Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 13 Nov 2018 02:18:04 +0500 Subject: [PATCH 5/6] just for history: attempting to pretent cloudfare service --- mtprotoproxy.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 2787d51..e914ace 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -146,28 +146,43 @@ def debug_signal(signum, frame): # load advanced settings # if IPv6 avaliable, use it by default PREFER_IPV6 = config.get("PREFER_IPV6", socket.has_ipv6) + # disables tg->client trafic reencryption, faster but less secure FAST_MODE = config.get("FAST_MODE", True) + # doesn't allow to connect in not-secure mode SECURE_ONLY = config.get("SECURE_ONLY", False) + +# proxy bad clients to some host to make a detection harder +PRETEND_HTTPS = config.get("PRETEND_HTTPS", False) + # delay in seconds between stats printing STATS_PRINT_PERIOD = config.get("STATS_PRINT_PERIOD", 600) + # delay in seconds between middle proxy info updates PROXY_INFO_UPDATE_PERIOD = config.get("PROXY_INFO_UPDATE_PERIOD", 24*60*60) + # max socket buffer size to the client direction, the more the faster, but more RAM hungry TO_CLT_BUFSIZE = config.get("TO_CLT_BUFSIZE", 16384) + # max socket buffer size to the telegram servers direction TO_TG_BUFSIZE = config.get("TO_TG_BUFSIZE", 65536) + # keepalive period for clients in secs CLIENT_KEEPALIVE = config.get("CLIENT_KEEPALIVE", 10*60) + # drop client after this timeout if the handshake fail CLIENT_HANDSHAKE_TIMEOUT = config.get("CLIENT_HANDSHAKE_TIMEOUT", 10) + # if client doesn't confirm data for this number of seconds, it is dropped CLIENT_ACK_TIMEOUT = config.get("CLIENT_ACK_TIMEOUT", 5*60) + # telegram servers connect timeout in seconds TG_CONNECT_TIMEOUT = config.get("TG_CONNECT_TIMEOUT", 10) + # listen address for IPv4 LISTEN_ADDR_IPV4 = config.get("LISTEN_ADDR_IPV4", "0.0.0.0") + # listen address for IPv6 LISTEN_ADDR_IPV6 = config.get("LISTEN_ADDR_IPV6", "::") @@ -581,6 +596,50 @@ def write(self, msg, extra={}): return self.upstream.write(full_msg) +async def proxy_bad_client(reader_clt, writer_clt, handshake): + BUF_SIZE = 8192 + CONNECT_TIMEOUT = 10 + + async def connect_reader_to_writer(rd, wr): + try: + while True: + data = await rd.read(BUF_SIZE) + + if not data: + wr.write_eof() + await wr.drain() + return + else: + wr.write(data) + await wr.drain() + except (OSError, asyncio.streams.IncompleteReadError) as e: + pass + + try: + # + ADDR = "www.cloudflare.com" + PORT = 443 + task = asyncio.open_connection(ADDR, PORT, limit=BUF_SIZE) + reader_srv, writer_srv = await asyncio.wait_for(task, timeout=CONNECT_TIMEOUT) + writer_srv.write(handshake) + except ConnectionRefusedError as E: + return False + except (OSError, asyncio.TimeoutError) as E: + return False + + srv_to_clt = connect_reader_to_writer(reader_srv, writer_clt) + clt_to_srv = connect_reader_to_writer(reader_clt, writer_srv) + task_srv_to_clt = asyncio.ensure_future(srv_to_clt) + task_clt_to_srv = asyncio.ensure_future(clt_to_srv) + + await asyncio.wait([task_srv_to_clt, task_clt_to_srv], return_when=asyncio.FIRST_COMPLETED) + + task_srv_to_clt.cancel() + task_clt_to_srv.cancel() + + writer_srv.transport.abort() + + async def handle_handshake(reader, writer): handshake = await reader.readexactly(HANDSHAKE_LEN) @@ -612,10 +671,14 @@ async def handle_handshake(reader, writer): writer = CryptoWrappedStreamWriter(writer, encryptor) return reader, writer, proto_tag, user, dc_idx, enc_key + enc_iv - EMPTY_READ_BUF_SIZE = 4096 - while await reader.read(EMPTY_READ_BUF_SIZE): - # just consume all the data - pass + if not PRETEND_HTTPS: + EMPTY_READ_BUF_SIZE = 4096 + while await reader.read(EMPTY_READ_BUF_SIZE): + # just consume all the data + pass + else: + # proxy client to some host + await proxy_bad_client(reader, writer, handshake) return False From 5187725088af7ef9cd254bbda9d8fe1dc5d01c53 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 13 Nov 2018 02:18:13 +0500 Subject: [PATCH 6/6] Revert "just for history: attempting to pretent cloudfare service" This reverts commit dd1d0a6262fe474fd6848b9bec0d79832c33868f. --- mtprotoproxy.py | 71 +++---------------------------------------------- 1 file changed, 4 insertions(+), 67 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index e914ace..2787d51 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -146,43 +146,28 @@ def debug_signal(signum, frame): # load advanced settings # if IPv6 avaliable, use it by default PREFER_IPV6 = config.get("PREFER_IPV6", socket.has_ipv6) - # disables tg->client trafic reencryption, faster but less secure FAST_MODE = config.get("FAST_MODE", True) - # doesn't allow to connect in not-secure mode SECURE_ONLY = config.get("SECURE_ONLY", False) - -# proxy bad clients to some host to make a detection harder -PRETEND_HTTPS = config.get("PRETEND_HTTPS", False) - # delay in seconds between stats printing STATS_PRINT_PERIOD = config.get("STATS_PRINT_PERIOD", 600) - # delay in seconds between middle proxy info updates PROXY_INFO_UPDATE_PERIOD = config.get("PROXY_INFO_UPDATE_PERIOD", 24*60*60) - # max socket buffer size to the client direction, the more the faster, but more RAM hungry TO_CLT_BUFSIZE = config.get("TO_CLT_BUFSIZE", 16384) - # max socket buffer size to the telegram servers direction TO_TG_BUFSIZE = config.get("TO_TG_BUFSIZE", 65536) - # keepalive period for clients in secs CLIENT_KEEPALIVE = config.get("CLIENT_KEEPALIVE", 10*60) - # drop client after this timeout if the handshake fail CLIENT_HANDSHAKE_TIMEOUT = config.get("CLIENT_HANDSHAKE_TIMEOUT", 10) - # if client doesn't confirm data for this number of seconds, it is dropped CLIENT_ACK_TIMEOUT = config.get("CLIENT_ACK_TIMEOUT", 5*60) - # telegram servers connect timeout in seconds TG_CONNECT_TIMEOUT = config.get("TG_CONNECT_TIMEOUT", 10) - # listen address for IPv4 LISTEN_ADDR_IPV4 = config.get("LISTEN_ADDR_IPV4", "0.0.0.0") - # listen address for IPv6 LISTEN_ADDR_IPV6 = config.get("LISTEN_ADDR_IPV6", "::") @@ -596,50 +581,6 @@ def write(self, msg, extra={}): return self.upstream.write(full_msg) -async def proxy_bad_client(reader_clt, writer_clt, handshake): - BUF_SIZE = 8192 - CONNECT_TIMEOUT = 10 - - async def connect_reader_to_writer(rd, wr): - try: - while True: - data = await rd.read(BUF_SIZE) - - if not data: - wr.write_eof() - await wr.drain() - return - else: - wr.write(data) - await wr.drain() - except (OSError, asyncio.streams.IncompleteReadError) as e: - pass - - try: - # - ADDR = "www.cloudflare.com" - PORT = 443 - task = asyncio.open_connection(ADDR, PORT, limit=BUF_SIZE) - reader_srv, writer_srv = await asyncio.wait_for(task, timeout=CONNECT_TIMEOUT) - writer_srv.write(handshake) - except ConnectionRefusedError as E: - return False - except (OSError, asyncio.TimeoutError) as E: - return False - - srv_to_clt = connect_reader_to_writer(reader_srv, writer_clt) - clt_to_srv = connect_reader_to_writer(reader_clt, writer_srv) - task_srv_to_clt = asyncio.ensure_future(srv_to_clt) - task_clt_to_srv = asyncio.ensure_future(clt_to_srv) - - await asyncio.wait([task_srv_to_clt, task_clt_to_srv], return_when=asyncio.FIRST_COMPLETED) - - task_srv_to_clt.cancel() - task_clt_to_srv.cancel() - - writer_srv.transport.abort() - - async def handle_handshake(reader, writer): handshake = await reader.readexactly(HANDSHAKE_LEN) @@ -671,14 +612,10 @@ async def handle_handshake(reader, writer): writer = CryptoWrappedStreamWriter(writer, encryptor) return reader, writer, proto_tag, user, dc_idx, enc_key + enc_iv - if not PRETEND_HTTPS: - EMPTY_READ_BUF_SIZE = 4096 - while await reader.read(EMPTY_READ_BUF_SIZE): - # just consume all the data - pass - else: - # proxy client to some host - await proxy_bad_client(reader, writer, handshake) + EMPTY_READ_BUF_SIZE = 4096 + while await reader.read(EMPTY_READ_BUF_SIZE): + # just consume all the data + pass return False