From e1d592cd84ba26282fc5ce69b15c32c681b4e1d8 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 19 Jun 2018 21:51:02 +0500 Subject: [PATCH 01/22] enable port reuse on non-windows platforms --- mtprotoproxy.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index f404666..8543619 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -966,7 +966,7 @@ def loop_exception_handler(loop, context): def main(): init_stats() - if sys.platform == 'win32': + if sys.platform == "win32": loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) @@ -980,13 +980,15 @@ def main(): middle_proxy_updater_task = asyncio.Task(update_middle_proxy_info()) asyncio.ensure_future(middle_proxy_updater_task) - task_v4 = asyncio.start_server(handle_client_wrapper, - '0.0.0.0', PORT, limit=READ_BUF_SIZE, loop=loop) + reuse_port = (sys.platform != "win32") + + task_v4 = asyncio.start_server(handle_client_wrapper, '0.0.0.0', PORT, + limit=READ_BUF_SIZE, 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, limit=READ_BUF_SIZE, loop=loop) + task_v6 = asyncio.start_server(handle_client_wrapper, '::', PORT, + limit=READ_BUF_SIZE, reuse_port=reuse_port, loop=loop) server_v6 = loop.run_until_complete(task_v6) try: From 51c40903ab6e0f7621dc862b5dceea281a99638e Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Thu, 21 Jun 2018 10:19:38 +0500 Subject: [PATCH 02/22] allows to bind on privilleged ports --- Dockerfile | 3 ++- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 62be9c4..3545fb8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,13 @@ FROM alpine:3.6 RUN adduser tgproxy -u 10000 -D -RUN apk add --no-cache python3 py3-crypto ca-certificates +RUN apk add --no-cache python3 py3-crypto ca-certificates libcap COPY mtprotoproxy.py config.py /home/tgproxy/ COPY pyaes/*.py /home/tgproxy/pyaes/ RUN chown -R tgproxy:tgproxy /home/tgproxy +RUN setcap cap_net_bind_service=+ep /usr/bin/python3.6 USER tgproxy diff --git a/docker-compose.yml b/docker-compose.yml index f460e7a..0073e8e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,5 +3,5 @@ services: mtprotoproxy: build: . restart: unless-stopped - mem_limit: 1024m network_mode: "host" +# mem_limit: 1024m From d56c995ee2042c1c86b1d5d12f6c43a29ba17f13 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Fri, 22 Jun 2018 15:26:33 +0500 Subject: [PATCH 03/22] use uvloop if available --- mtprotoproxy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 8543619..b3a229b 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -13,6 +13,11 @@ import sys import re import runpy +try: + import uvloop + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) +except ImportError: + pass try: from Crypto.Cipher import AES From d2ff0f61e4b99cc1c9b38c87944782f77cd1a15c Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 26 Jun 2018 03:24:45 +0500 Subject: [PATCH 04/22] add handshake timeout, refactor client handling a bit --- mtprotoproxy.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index b3a229b..3d16f8f 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -72,6 +72,7 @@ else: PORT = config["PORT"] USERS = config["USERS"] +AD_TAG = bytes.fromhex(config.get("AD_TAG", "")) # load advanced settings PREFER_IPV6 = config.get("PREFER_IPV6", socket.has_ipv6) @@ -82,7 +83,7 @@ PROXY_INFO_UPDATE_PERIOD = config.get("PROXY_INFO_UPDATE_PERIOD", 60*60*24) READ_BUF_SIZE = config.get("READ_BUF_SIZE", 16384) WRITE_BUF_SIZE = config.get("WRITE_BUF_SIZE", 65536) CLIENT_KEEPALIVE = config.get("CLIENT_KEEPALIVE", 60*30) -AD_TAG = bytes.fromhex(config.get("AD_TAG", "")) +CLIENT_HANDSHAKE_TIMEOUT = config.get("CLIENT_HANDSHAKE_TIMEOUT", 10) TG_DATACENTER_PORT = 443 @@ -728,9 +729,13 @@ async def handle_client(reader_clt, writer_clt): set_keepalive(writer_clt.get_extra_info("socket"), CLIENT_KEEPALIVE) set_bufsizes(writer_clt.get_extra_info("socket")) - clt_data = await handle_handshake(reader_clt, writer_clt) + try: + clt_data = await asyncio.wait_for(handle_handshake(reader_clt, writer_clt), + timeout=CLIENT_HANDSHAKE_TIMEOUT) + except asyncio.TimeoutError: + return + if not clt_data: - writer_clt.transport.abort() return reader_clt, writer_clt, proto_tag, user, dc_idx, enc_key_and_iv = clt_data @@ -747,7 +752,6 @@ async def handle_client(reader_clt, writer_clt): tg_data = await do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port) if not tg_data: - writer_clt.transport.abort() return reader_tg, writer_tg = tg_data @@ -800,14 +804,19 @@ async def handle_client(reader_clt, writer_clt): wr.transport.abort() update_stats(user, curr_connects_x2=-1) - asyncio.ensure_future(connect_reader_to_writer(reader_tg, writer_clt, user)) - asyncio.ensure_future(connect_reader_to_writer(reader_clt, writer_tg, user)) + task_tg_to_clt = connect_reader_to_writer(reader_tg, writer_clt, user) + task_clt_to_tg = connect_reader_to_writer(reader_clt, writer_tg, user) + + await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED) + writer_tg.transport.abort() async def handle_client_wrapper(reader, writer): try: await handle_client(reader, writer) except (asyncio.IncompleteReadError, ConnectionResetError, TimeoutError): + pass + finally: writer.transport.abort() From 9077ceb471b7ff94646db97a1f640d771ddc4221 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 26 Jun 2018 03:38:11 +0500 Subject: [PATCH 05/22] simplify current connects counting --- mtprotoproxy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 3d16f8f..c0528ee 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -153,13 +153,13 @@ def init_stats(): stats = {user: collections.Counter() for user in USERS} -def update_stats(user, connects=0, curr_connects_x2=0, octets=0): +def update_stats(user, connects=0, curr_connects=0, octets=0): global stats if user not in stats: stats[user] = collections.Counter() - stats[user].update(connects=connects, curr_connects_x2=curr_connects_x2, + stats[user].update(connects=connects, curr_connects=curr_connects, octets=octets) @@ -779,7 +779,6 @@ async def handle_client(reader_clt, writer_clt): return async def connect_reader_to_writer(rd, wr, user): - update_stats(user, curr_connects_x2=1) try: while True: data = await rd.read(READ_BUF_SIZE) @@ -802,12 +801,13 @@ async def handle_client(reader_clt, writer_clt): pass finally: wr.transport.abort() - update_stats(user, curr_connects_x2=-1) task_tg_to_clt = connect_reader_to_writer(reader_tg, writer_clt, user) task_clt_to_tg = connect_reader_to_writer(reader_clt, writer_tg, user) + update_stats(user, curr_connects=1) await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED) + update_stats(user, curr_connects=-1) writer_tg.transport.abort() @@ -828,7 +828,7 @@ async def stats_printer(): print("Stats for", time.strftime("%d.%m.%Y %H:%M:%S")) for user, stat in stats.items(): print("%s: %d connects (%d current), %.2f MB" % ( - user, stat["connects"], stat["curr_connects_x2"] // 2, + user, stat["connects"], stat["curr_connects"], stat["octets"] / 1000000)) print(flush=True) From bd3d9731d730ba411d9d567ca220b76832ad8778 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 26 Jun 2018 11:48:58 +0500 Subject: [PATCH 06/22] if the handshake failed, just consume all the data --- mtprotoproxy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index c0528ee..ba9cef8 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -487,6 +487,11 @@ async def handle_handshake(reader, writer): reader = CryptoWrappedStreamReader(reader, decryptor) writer = CryptoWrappedStreamWriter(writer, encryptor) return reader, writer, proto_tag, user, dc_idx, enc_key + enc_iv + + while True: + # just consume all the data + await reader.read(READ_BUF_SIZE) + return False From accba06b45fe365053eea3d3dbb2122d578d3001 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 26 Jun 2018 20:17:52 +0500 Subject: [PATCH 07/22] count client stats only for successfull clients --- mtprotoproxy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index ba9cef8..e4e6f40 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -745,8 +745,6 @@ async def handle_client(reader_clt, writer_clt): reader_clt, writer_clt, proto_tag, user, dc_idx, enc_key_and_iv = clt_data - update_stats(user, connects=1) - if not USE_MIDDLE_PROXY: if FAST_MODE: tg_data = await do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=enc_key_and_iv) @@ -759,6 +757,8 @@ async def handle_client(reader_clt, writer_clt): if not tg_data: return + update_stats(user, connects=1) + reader_tg, writer_tg = tg_data if not USE_MIDDLE_PROXY and FAST_MODE: From ed088d94499bbb6097c5e9470d29b7b4cc8867ec Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 26 Jun 2018 20:21:51 +0500 Subject: [PATCH 08/22] revert the last commit --- mtprotoproxy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index e4e6f40..ba9cef8 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -745,6 +745,8 @@ async def handle_client(reader_clt, writer_clt): reader_clt, writer_clt, proto_tag, user, dc_idx, enc_key_and_iv = clt_data + update_stats(user, connects=1) + if not USE_MIDDLE_PROXY: if FAST_MODE: tg_data = await do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=enc_key_and_iv) @@ -757,8 +759,6 @@ async def handle_client(reader_clt, writer_clt): if not tg_data: return - update_stats(user, connects=1) - reader_tg, writer_tg = tg_data if not USE_MIDDLE_PROXY and FAST_MODE: From 444a1876b6072613fee77e2492c87d196e0a4b3c Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 26 Jun 2018 20:39:43 +0500 Subject: [PATCH 09/22] refactor task canceling a bit --- mtprotoproxy.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index ba9cef8..86a4860 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -801,18 +801,22 @@ async def handle_client(reader_clt, writer_clt): update_stats(user, octets=len(data)) wr.write(data, extra) await wr.drain() - except (OSError, AttributeError, asyncio.streams.IncompleteReadError) as e: + except (OSError, asyncio.streams.IncompleteReadError) as e: # print_err(e) pass finally: wr.transport.abort() - task_tg_to_clt = connect_reader_to_writer(reader_tg, writer_clt, user) - task_clt_to_tg = connect_reader_to_writer(reader_clt, writer_tg, user) + task_tg_to_clt = asyncio.ensure_future(connect_reader_to_writer(reader_tg, writer_clt, user)) + task_clt_to_tg = asyncio.ensure_future(connect_reader_to_writer(reader_clt, writer_tg, user)) update_stats(user, curr_connects=1) await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED) update_stats(user, curr_connects=-1) + + task_tg_to_clt.cancel() + task_clt_to_tg.cancel() + writer_tg.transport.abort() From a20b1c99293b67ff3aa0c44a1687691d6a7bf92d Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Tue, 26 Jun 2018 22:53:46 +0500 Subject: [PATCH 10/22] simplify dissconnect logic --- mtprotoproxy.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 86a4860..2c28dcb 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -795,7 +795,6 @@ async def handle_client(reader_clt, writer_clt): if not data: wr.write_eof() await wr.drain() - wr.close() return else: update_stats(user, octets=len(data)) @@ -804,8 +803,6 @@ async def handle_client(reader_clt, writer_clt): except (OSError, asyncio.streams.IncompleteReadError) as e: # print_err(e) pass - finally: - wr.transport.abort() task_tg_to_clt = asyncio.ensure_future(connect_reader_to_writer(reader_tg, writer_clt, user)) task_clt_to_tg = asyncio.ensure_future(connect_reader_to_writer(reader_clt, writer_tg, user)) From 32d3bffc7b8d440849de8e76e03a64225893c00c Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 01:04:06 +0500 Subject: [PATCH 11/22] Revert "simplify dissconnect logic". The idea with task cancelation doesn't work This reverts commit a20b1c99293b67ff3aa0c44a1687691d6a7bf92d. --- mtprotoproxy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 2c28dcb..86a4860 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -795,6 +795,7 @@ async def handle_client(reader_clt, writer_clt): if not data: wr.write_eof() await wr.drain() + wr.close() return else: update_stats(user, octets=len(data)) @@ -803,6 +804,8 @@ async def handle_client(reader_clt, writer_clt): except (OSError, asyncio.streams.IncompleteReadError) as e: # print_err(e) pass + finally: + wr.transport.abort() task_tg_to_clt = asyncio.ensure_future(connect_reader_to_writer(reader_tg, writer_clt, user)) task_clt_to_tg = asyncio.ensure_future(connect_reader_to_writer(reader_clt, writer_tg, user)) From b74079c4337939b695dc68c2a6979112eb368297 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 01:05:08 +0500 Subject: [PATCH 12/22] Revert "refactor task canceling a bit". The idea with the task cancelation doesn't work This reverts commit 444a1876b6072613fee77e2492c87d196e0a4b3c. --- mtprotoproxy.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 86a4860..ba9cef8 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -801,22 +801,18 @@ async def handle_client(reader_clt, writer_clt): update_stats(user, octets=len(data)) wr.write(data, extra) await wr.drain() - except (OSError, asyncio.streams.IncompleteReadError) as e: + except (OSError, AttributeError, asyncio.streams.IncompleteReadError) as e: # print_err(e) pass finally: wr.transport.abort() - task_tg_to_clt = asyncio.ensure_future(connect_reader_to_writer(reader_tg, writer_clt, user)) - task_clt_to_tg = asyncio.ensure_future(connect_reader_to_writer(reader_clt, writer_tg, user)) + task_tg_to_clt = connect_reader_to_writer(reader_tg, writer_clt, user) + task_clt_to_tg = connect_reader_to_writer(reader_clt, writer_tg, user) update_stats(user, curr_connects=1) await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED) update_stats(user, curr_connects=-1) - - task_tg_to_clt.cancel() - task_clt_to_tg.cancel() - writer_tg.transport.abort() From 5f35b4ed0a259429cc9a50e5c56e7729de40f9c9 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 01:14:44 +0500 Subject: [PATCH 13/22] add debugging signal --- mtprotoproxy.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index ba9cef8..bc83a28 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -12,6 +12,7 @@ import binascii import sys import re import runpy +import signal try: import uvloop @@ -65,6 +66,13 @@ except (ValueError, OSError): except ImportError: pass + +def debug_signal(signum, frame): + import pdb + pdb.set_trace() + +signal.signal(signal.SIGUSR1, debug_signal) + if len(sys.argv) > 1: config = runpy.run_path(sys.argv[1]) else: From d74bb68f031b955474a9800e4e72a2f93d85bbd8 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 11:11:45 +0500 Subject: [PATCH 14/22] Revert "Revert "refactor task canceling a bit". The idea with the task cancelation doesn't work" This reverts commit b74079c4337939b695dc68c2a6979112eb368297. --- mtprotoproxy.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index bc83a28..a470108 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -809,18 +809,22 @@ async def handle_client(reader_clt, writer_clt): update_stats(user, octets=len(data)) wr.write(data, extra) await wr.drain() - except (OSError, AttributeError, asyncio.streams.IncompleteReadError) as e: + except (OSError, asyncio.streams.IncompleteReadError) as e: # print_err(e) pass finally: wr.transport.abort() - task_tg_to_clt = connect_reader_to_writer(reader_tg, writer_clt, user) - task_clt_to_tg = connect_reader_to_writer(reader_clt, writer_tg, user) + task_tg_to_clt = asyncio.ensure_future(connect_reader_to_writer(reader_tg, writer_clt, user)) + task_clt_to_tg = asyncio.ensure_future(connect_reader_to_writer(reader_clt, writer_tg, user)) update_stats(user, curr_connects=1) await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED) update_stats(user, curr_connects=-1) + + task_tg_to_clt.cancel() + task_clt_to_tg.cancel() + writer_tg.transport.abort() From 2e86308e90c9febbd418600dee306b684e9f8e55 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 11:11:50 +0500 Subject: [PATCH 15/22] Revert "Revert "simplify dissconnect logic". The idea with task cancelation doesn't work" This reverts commit 32d3bffc7b8d440849de8e76e03a64225893c00c. --- mtprotoproxy.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index a470108..0363482 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -803,7 +803,6 @@ async def handle_client(reader_clt, writer_clt): if not data: wr.write_eof() await wr.drain() - wr.close() return else: update_stats(user, octets=len(data)) @@ -812,8 +811,6 @@ async def handle_client(reader_clt, writer_clt): except (OSError, asyncio.streams.IncompleteReadError) as e: # print_err(e) pass - finally: - wr.transport.abort() task_tg_to_clt = asyncio.ensure_future(connect_reader_to_writer(reader_tg, writer_clt, user)) task_clt_to_tg = asyncio.ensure_future(connect_reader_to_writer(reader_clt, writer_tg, user)) From 7eea7d3201f6c9e89f63058ab5fed59ed2ee0f64 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 11:13:42 +0500 Subject: [PATCH 16/22] replace infinite loop with timeout with while loop, when the client is bad --- mtprotoproxy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 0363482..39d7afe 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -496,9 +496,9 @@ async def handle_handshake(reader, writer): writer = CryptoWrappedStreamWriter(writer, encryptor) return reader, writer, proto_tag, user, dc_idx, enc_key + enc_iv - while True: + while await reader.read(READ_BUF_SIZE): # just consume all the data - await reader.read(READ_BUF_SIZE) + pass return False From 71e3206b1916889bd59c7554dd4feb737a374ad3 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 13:33:51 +0500 Subject: [PATCH 17/22] check if signal exists before placing it. It can absent in some OSes, like Windows --- mtprotoproxy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 39d7afe..cc2e608 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -66,12 +66,12 @@ except (ValueError, OSError): except ImportError: pass +if hasattr(signal, 'SIGUSR1'): + def debug_signal(signum, frame): + import pdb + pdb.set_trace() -def debug_signal(signum, frame): - import pdb - pdb.set_trace() - -signal.signal(signal.SIGUSR1, debug_signal) + signal.signal(signal.SIGUSR1, debug_signal) if len(sys.argv) > 1: config = runpy.run_path(sys.argv[1]) From 63b689e3bf61cd701a4cdfb4a670cc913f1906b8 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 18:25:40 +0500 Subject: [PATCH 18/22] Add a section about advanced usage --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cd1689..e6be07c 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,11 @@ To advertise a channel get a tag from **@MTProxybot** and write it to *config.py ## Performance ## The proxy performance should be enough to comfortably serve about 4 000 simultaneous users on -the smallest VDS instance with 1 CPU core and 1024MB RAM. +the VDS instance with 1 CPU core and 1024MB RAM. + +## Advanced Usage ## + +The proxy can be launched: +- with a custom config: `python3 mtprotoproxy.py [configfile]` +- several times, clients will be automaticaly balanced between instances +- using *PyPy* interprteter From ec1c6b4fb6e838f97657dd9c96147dc690237c11 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Wed, 27 Jun 2018 20:04:05 +0500 Subject: [PATCH 19/22] we need at least one undocumented launching way :) --- mtprotoproxy.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index cc2e608..022eb22 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -73,10 +73,18 @@ if hasattr(signal, 'SIGUSR1'): signal.signal(signal.SIGUSR1, debug_signal) -if len(sys.argv) > 1: +if len(sys.argv) < 2: + config = runpy.run_module("config") +elif len(sys.argv) == 2: config = runpy.run_path(sys.argv[1]) else: - config = runpy.run_module("config") + # undocumented way of launching + config = {} + config["PORT"] = int(sys.argv[1]) + secrets = sys.argv[2].split(",") + config["USERS"] = {"user%d" % i: secrets[i].zfill(32) for i in range(len(secrets))} + if len(sys.argv) > 3: + config["AD_TAG"] = sys.argv[3] PORT = config["PORT"] USERS = config["USERS"] From 532021ab8790285d72b25c98278d844c9199cba1 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Thu, 28 Jun 2018 20:47:12 +0500 Subject: [PATCH 20/22] support for cryptography module and advise to use it --- mtprotoproxy.py | 61 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 022eb22..128d7fd 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -20,7 +20,46 @@ try: except ImportError: pass -try: + +def try_use_cryptography_module(): + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + + def create_aes_ctr(key, iv): + class EncryptorAdapter: + def __init__(self, cipher): + self.encryptor = cipher.encryptor() + self.decryptor = cipher.decryptor() + + def encrypt(self, data): + return self.encryptor.update(data) + + def decrypt(self, data): + return self.decryptor.update(data) + + iv_bytes = int.to_bytes(iv, 16, "big") + cipher = Cipher(algorithms.AES(key), modes.CTR(iv_bytes), default_backend()) + return EncryptorAdapter(cipher) + + def create_aes_cbc(key, iv): + class EncryptorAdapter: + def __init__(self, cipher): + self.encryptor = cipher.encryptor() + self.decryptor = cipher.decryptor() + + def encrypt(self, data): + return self.encryptor.update(data) + + def decrypt(self, data): + return self.decryptor.update(data) + + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend()) + return EncryptorAdapter(cipher) + + return create_aes_ctr, create_aes_cbc + + +def try_use_pycrypto_or_pycryptodome_module(): from Crypto.Cipher import AES from Crypto.Util import Counter @@ -31,11 +70,16 @@ try: def create_aes_cbc(key, iv): return AES.new(key, AES.MODE_CBC, iv) -except ImportError: - print("Failed to find pycryptodome or pycrypto, using slow AES implementation", - flush=True, file=sys.stderr) + return create_aes_ctr, create_aes_cbc + + +def use_slow_bundled_cryptography_module(): import pyaes + msg = "To make the program a *lot* faster, please install cryptography module: " + msg += "pip install cryptography\n" + print(msg, flush=True, file=sys.stderr) + def create_aes_ctr(key, iv): ctr = pyaes.Counter(iv) return pyaes.AESModeOfOperationCTR(key, ctr) @@ -55,8 +99,17 @@ except ImportError: mode = pyaes.AESModeOfOperationCBC(key, iv) return EncryptorAdapter(mode) + return create_aes_ctr, create_aes_cbc +try: + create_aes_ctr, create_aes_cbc = try_use_cryptography_module() +except ImportError: + try: + create_aes_ctr, create_aes_cbc = try_use_pycrypto_or_pycryptodome_module() + except ImportError: + create_aes_ctr, create_aes_cbc = use_slow_bundled_cryptography_module() + try: import resource soft_fd_limit, hard_fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE) From 3477402c0d781bc536e5201ed2a79d2fa2fc0180 Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Fri, 29 Jun 2018 01:07:16 +0500 Subject: [PATCH 21/22] use cryptography module in docker file, do not copy pyaes --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3545fb8..3622790 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,9 @@ FROM alpine:3.6 RUN adduser tgproxy -u 10000 -D -RUN apk add --no-cache python3 py3-crypto ca-certificates libcap +RUN apk add --no-cache python3 py3-cryptography ca-certificates libcap COPY mtprotoproxy.py config.py /home/tgproxy/ -COPY pyaes/*.py /home/tgproxy/pyaes/ RUN chown -R tgproxy:tgproxy /home/tgproxy RUN setcap cap_net_bind_service=+ep /usr/bin/python3.6 From 03f7ca1d4c6ea0d39d34482ca6f3eabe4d31f34f Mon Sep 17 00:00:00 2001 From: Alexander Bersenev Date: Fri, 29 Jun 2018 02:00:46 +0500 Subject: [PATCH 22/22] more reliable logic to check reuseport availability --- mtprotoproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 128d7fd..ee5af3e 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -1069,7 +1069,7 @@ def main(): middle_proxy_updater_task = asyncio.Task(update_middle_proxy_info()) asyncio.ensure_future(middle_proxy_updater_task) - reuse_port = (sys.platform != "win32") + reuse_port = hasattr(socket, "SO_REUSEPORT") task_v4 = asyncio.start_server(handle_client_wrapper, '0.0.0.0', PORT, limit=READ_BUF_SIZE, reuse_port=reuse_port, loop=loop)