From 6d359e20695040c99fd0701837977c28a561b242 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:07:46 +0000 Subject: [PATCH 1/4] Initial plan From cc2276f20a2bd6260a4ad520403703c396e04ab0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:10:56 +0000 Subject: [PATCH 2/4] Apply PR #184 and #308 changes: Docker improvements and connection pool disable Co-authored-by: xrh0905 <29017419+xrh0905@users.noreply.github.com> --- README.md | 5 +-- __pycache__/config.cpython-312.pyc | Bin 0 -> 728 bytes __pycache__/mtprotoproxy.cpython-312.pyc | Bin 0 -> 112663 bytes config.py | 28 +++++++-------- docker-compose.yml | 16 ++++----- mtprotoproxy.py | 42 +++++++++++------------ 6 files changed, 44 insertions(+), 47 deletions(-) create mode 100644 __pycache__/config.cpython-312.pyc create mode 100644 __pycache__/mtprotoproxy.cpython-312.pyc diff --git a/README.md b/README.md index 3cde3dcb..623ab4f2 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ Fast and simple to setup MTProto proxy written in Python. 1. `git clone -b stable https://github.com/alexbers/mtprotoproxy.git; cd mtprotoproxy` 2. *(optional, recommended)* edit *config.py*, set **PORT**, **USERS** and **AD_TAG** -3. `docker-compose up -d` (or just `python3 mtprotoproxy.py` if you don't like Docker) -4. *(optional, get a link to share the proxy)* `docker-compose logs` +3. `docker build -t mtprotoproxy .` +4. `docker-compose up -d` (or just `python3 mtprotoproxy.py` if you don't like Docker) +5. *(optional, get a link to share the proxy)* `docker-compose logs` ![Demo](https://alexbers.com/mtprotoproxy/install_demo_v2.gif) diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..976f5fa77c640ee3f1c8a253bb96b40b80e36b59 GIT binary patch literal 728 zcmX@j%ge<81P84xG9NNAFgylvV1O0M_^iXoz%ZR5g&~D8harj~g(-?Lg*lZWiv^|- zL1(ce*f3TKOA701pxGdh$r!~#f=;#+c9_m6Ruc4aAn9WxK_6!d7u-H}67+GW@WAzP zR5EJvz65#JFLO6A;BGOMq~Br-agXO0*k zG)XoxO-wO0OfxqzGBPty(d4|vlwW*{CBQ!@Qe%>wS^wg4Dtf9fKLBU0A zKzA2`BC&`AL~sI$l?Wu1->x`S>b3x1MGOu+5*9SHR dE}nj=PN{ymPPrKz7nEEsbGkON6$t`e0s!wtjJf~- literal 0 HcmV?d00001 diff --git a/__pycache__/mtprotoproxy.cpython-312.pyc b/__pycache__/mtprotoproxy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d87a9f19e27bf49ccdadce23dee8ac80ab93abd GIT binary patch literal 112663 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 literal 0 HcmV?d00001 diff --git a/config.py b/config.py index 54b614ec..3d303822 100644 --- a/config.py +++ b/config.py @@ -1,27 +1,23 @@ +import os PORT = 443 # name -> secret (32 hex chars) USERS = { - "tg": "00000000000000000000000000000001", - # "tg2": "0123456789abcdef0123456789abcdef", + "tg": os.environ.get("TG_KEY", "00000000000000000000000000000001"), +# "tg2": "0123456789abcdef0123456789abcdef", } -MODES = { - # Classic mode, easy to detect - "classic": False, +# Makes the proxy harder to detect +# Can be incompatible with very old clients +SECURE_ONLY = os.environ.get('SECURE_ONLY', True) - # Makes the proxy harder to detect - # Can be incompatible with very old clients - "secure": False, +# Makes the proxy even more hard to detect +# Compatible only with the recent clients +TLS_ONLY = os.environ.get('TLS_ONLY', True) - # Makes the proxy even more hard to detect - # Can be incompatible with old clients - "tls": True -} - -# The domain for TLS mode, bad clients are proxied there +# The domain for TLS, bad clients are proxied there # Use random existing domain, proxy checks it on start -# TLS_DOMAIN = "www.google.com" +TLS_DOMAIN = os.environ.get('TLS_DOMAIN', 'www.google.com') # Tag for advertising, obtainable from @MTProxybot -# AD_TAG = "3c09c680b76ee91a4c25ad51f742267d" +AD_TAG = os.environ.get('AD_TAG', '3c09c680b76ee91a4c25ad51f742267d') diff --git a/docker-compose.yml b/docker-compose.yml index f490bf66..02847379 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,16 @@ version: '2.0' services: mtprotoproxy: - build: . + image: mtprotoproxy restart: unless-stopped network_mode: "host" + environment: + - TG_KEY=00000000000000000000000000000001 + - SECURE_ONLY=true + - TLS_ONLY=true + - TLS_DOMAIN=www.drive.google.com + - AD_TAG=3c09c680b76ee91a4c25ad51f742267d + volumes: - ./config.py:/home/tgproxy/config.py - ./mtprotoproxy.py:/home/tgproxy/mtprotoproxy.py - - /etc/localtime:/etc/localtime:ro - logging: - driver: "json-file" - options: - max-file: "10" - max-size: "10m" -# mem_limit: 1024m diff --git a/mtprotoproxy.py b/mtprotoproxy.py index fb686ab2..05710e7e 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -509,27 +509,27 @@ def register_host_port(self, host, port, init_func): self.pools[(host, port, init_func)].append(connect_task) async def get_connection(self, host, port, init_func=None): - self.register_host_port(host, port, init_func) - - ret = None - for task in self.pools[(host, port, init_func)][::]: - if task.done(): - if task.exception(): - self.pools[(host, port, init_func)].remove(task) - continue - - reader, writer, *other = task.result() - if writer.transport.is_closing(): - self.pools[(host, port, init_func)].remove(task) - continue - - if not ret: - self.pools[(host, port, init_func)].remove(task) - ret = (reader, writer, *other) - - self.register_host_port(host, port, init_func) - if ret: - return ret + # self.register_host_port(host, port, init_func) + + # ret = None + # for task in self.pools[(host, port, init_func)][::]: + # if task.done(): + # if task.exception(): + # self.pools[(host, port, init_func)].remove(task) + # continue + + # reader, writer, *other = task.result() + # if writer.transport.is_closing(): + # self.pools[(host, port, init_func)].remove(task) + # continue + + # if not ret: + # self.pools[(host, port, init_func)].remove(task) + # ret = (reader, writer, *other) + + # self.register_host_port(host, port, init_func) + # if ret: + # return ret return await self.open_tg_connection(host, port, init_func) From c347f9e1f5ff36c03e64c636e6171599638efff1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:12:29 +0000 Subject: [PATCH 3/4] Add .gitignore and apply code formatting from PR #308 Co-authored-by: xrh0905 <29017419+xrh0905@users.noreply.github.com> --- .gitignore | 10 + __pycache__/config.cpython-312.pyc | Bin 728 -> 0 bytes __pycache__/mtprotoproxy.cpython-312.pyc | Bin 112663 -> 0 bytes mtprotoproxy.py | 678 ++++++++++++++++------- 4 files changed, 478 insertions(+), 210 deletions(-) create mode 100644 .gitignore delete mode 100644 __pycache__/config.cpython-312.pyc delete mode 100644 __pycache__/mtprotoproxy.cpython-312.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0cd10812 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.so +*.egg +*.egg-info/ +dist/ +build/ diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc deleted file mode 100644 index 976f5fa77c640ee3f1c8a253bb96b40b80e36b59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 728 zcmX@j%ge<81P84xG9NNAFgylvV1O0M_^iXoz%ZR5g&~D8harj~g(-?Lg*lZWiv^|- zL1(ce*f3TKOA701pxGdh$r!~#f=;#+c9_m6Ruc4aAn9WxK_6!d7u-H}67+GW@WAzP zR5EJvz65#JFLO6A;BGOMq~Br-agXO0*k zG)XoxO-wO0OfxqzGBPty(d4|vlwW*{CBQ!@Qe%>wS^wg4Dtf9fKLBU0A zKzA2`BC&`AL~sI$l?Wu1->x`S>b3x1MGOu+5*9SHR dE}nj=PN{ymPPrKz7nEEsbGkON6$t`e0s!wtjJf~- diff --git a/__pycache__/mtprotoproxy.cpython-312.pyc b/__pycache__/mtprotoproxy.cpython-312.pyc deleted file mode 100644 index 3d87a9f19e27bf49ccdadce23dee8ac80ab93abd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112663 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 diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 05710e7e..2289c449 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -24,37 +24,53 @@ TG_DATACENTER_PORT = 443 TG_DATACENTERS_V4 = [ - "149.154.175.50", "149.154.167.51", "149.154.175.100", - "149.154.167.91", "149.154.171.5" + "149.154.175.50", + "149.154.167.51", + "149.154.175.100", + "149.154.167.91", + "149.154.171.5", ] TG_DATACENTERS_V6 = [ - "2001:b28:f23d:f001::a", "2001:67c:04e8:f002::a", "2001:b28:f23d:f003::a", - "2001:67c:04e8:f004::a", "2001:b28:f23f:f005::a" + "2001:b28:f23d:f001::a", + "2001:67c:04e8:f002::a", + "2001:b28:f23d:f003::a", + "2001:67c:04e8:f004::a", + "2001:b28:f23f:f005::a", ] # This list will be updated in the runtime TG_MIDDLE_PROXIES_V4 = { - 1: [("149.154.175.50", 8888)], -1: [("149.154.175.50", 8888)], - 2: [("149.154.161.144", 8888)], -2: [("149.154.161.144", 8888)], - 3: [("149.154.175.100", 8888)], -3: [("149.154.175.100", 8888)], - 4: [("91.108.4.136", 8888)], -4: [("149.154.165.109", 8888)], - 5: [("91.108.56.183", 8888)], -5: [("91.108.56.183", 8888)] + 1: [("149.154.175.50", 8888)], + -1: [("149.154.175.50", 8888)], + 2: [("149.154.161.144", 8888)], + -2: [("149.154.161.144", 8888)], + 3: [("149.154.175.100", 8888)], + -3: [("149.154.175.100", 8888)], + 4: [("91.108.4.136", 8888)], + -4: [("149.154.165.109", 8888)], + 5: [("91.108.56.183", 8888)], + -5: [("91.108.56.183", 8888)], } TG_MIDDLE_PROXIES_V6 = { - 1: [("2001:b28:f23d:f001::d", 8888)], -1: [("2001:b28:f23d:f001::d", 8888)], - 2: [("2001:67c:04e8:f002::d", 80)], -2: [("2001:67c:04e8:f002::d", 80)], - 3: [("2001:b28:f23d:f003::d", 8888)], -3: [("2001:b28:f23d:f003::d", 8888)], - 4: [("2001:67c:04e8:f004::d", 8888)], -4: [("2001:67c:04e8:f004::d", 8888)], - 5: [("2001:b28:f23f:f005::d", 8888)], -5: [("2001:b28:f23f:f005::d", 8888)] + 1: [("2001:b28:f23d:f001::d", 8888)], + -1: [("2001:b28:f23d:f001::d", 8888)], + 2: [("2001:67c:04e8:f002::d", 80)], + -2: [("2001:67c:04e8:f002::d", 80)], + 3: [("2001:b28:f23d:f003::d", 8888)], + -3: [("2001:b28:f23d:f003::d", 8888)], + 4: [("2001:67c:04e8:f004::d", 8888)], + -4: [("2001:67c:04e8:f004::d", 8888)], + 5: [("2001:b28:f23f:f005::d", 8888)], + -5: [("2001:b28:f23f:f005::d", 8888)], } PROXY_SECRET = bytes.fromhex( - "c4f9faca9678e6bb48ad6c7e2ce5c0d24430645d554addeb55419e034da62721" + - "d046eaab6e52ab14a95a443ecfb3463e79a05a66612adf9caeda8be9a80da698" + - "6fb0a6ff387af84d88ef3a6413713e5c3377f6e1a3d47d99f5e0c56eece8f05c" + - "54c490b079e31bef82ff0ee8f2b0a32756d249c5f21269816cb7061b265db212" + "c4f9faca9678e6bb48ad6c7e2ce5c0d24430645d554addeb55419e034da62721" + + "d046eaab6e52ab14a95a443ecfb3463e79a05a66612adf9caeda8be9a80da698" + + "6fb0a6ff387af84d88ef3a6413713e5c3377f6e1a3d47d99f5e0c56eece8f05c" + + "54c490b079e31bef82ff0ee8f2b0a32756d249c5f21269816cb7061b265db212" ) SKIP_LEN = 8 @@ -75,7 +91,7 @@ PADDING_FILLER = b"\x04\x00\x00\x00" MIN_MSG_LEN = 12 -MAX_MSG_LEN = 2 ** 24 +MAX_MSG_LEN = 2**24 STAT_DURATION_BUCKETS = [0.1, 0.5, 1, 2, 5, 15, 60, 300, 600, 1800, 2**31 - 1] @@ -111,7 +127,9 @@ def init_config(): conf_dict = {} conf_dict["PORT"] = int(sys.argv[1]) secrets = sys.argv[2].split(",") - conf_dict["USERS"] = {"user%d" % i: secrets[i].zfill(32) for i in range(len(secrets))} + conf_dict["USERS"] = { + "user%d" % i: secrets[i].zfill(32) for i in range(len(secrets)) + } conf_dict["MODES"] = {"classic": False, "secure": True, "tls": True} if len(sys.argv) > 3: conf_dict["AD_TAG"] = sys.argv[3] @@ -122,14 +140,17 @@ def init_config(): conf_dict = {k: v for k, v in conf_dict.items() if k.isupper()} conf_dict.setdefault("PORT", 3256) - conf_dict.setdefault("USERS", {"tg": "00000000000000000000000000000000"}) + conf_dict.setdefault("USERS", {"tg": "00000000000000000000000000000000"}) conf_dict["AD_TAG"] = bytes.fromhex(conf_dict.get("AD_TAG", "")) for user, secret in conf_dict["USERS"].items(): if not re.fullmatch("[0-9a-fA-F]{32}", secret): fixed_secret = re.sub(r"[^0-9a-fA-F]", "", secret).zfill(32)[:32] - print_err("Bad secret for user %s, should be 32 hex chars, got %s. " % (user, secret)) + print_err( + "Bad secret for user %s, should be 32 hex chars, got %s. " + % (user, secret) + ) print_err("Changing it to %s" % fixed_secret) conf_dict["USERS"][user] = fixed_secret @@ -218,7 +239,9 @@ def init_config(): # expiration date for users in format of day/month/year conf_dict.setdefault("USER_EXPIRATIONS", {}) for user in conf_dict["USER_EXPIRATIONS"]: - expiration = datetime.datetime.strptime(conf_dict["USER_EXPIRATIONS"][user], "%d/%m/%Y") + expiration = datetime.datetime.strptime( + conf_dict["USER_EXPIRATIONS"][user], "%d/%m/%Y" + ) conf_dict["USER_EXPIRATIONS"][user] = expiration # the data quota for user @@ -237,13 +260,15 @@ def init_config(): conf_dict.setdefault("STATS_PRINT_PERIOD", 600) # delay in seconds between middle proxy info updates - conf_dict.setdefault("PROXY_INFO_UPDATE_PERIOD", 24*60*60) + conf_dict.setdefault("PROXY_INFO_UPDATE_PERIOD", 24 * 60 * 60) # delay in seconds between time getting, zero means disabled - conf_dict.setdefault("GET_TIME_PERIOD", 10*60) + conf_dict.setdefault("GET_TIME_PERIOD", 10 * 60) # delay in seconds between getting the length of certificate on the mask host - conf_dict.setdefault("GET_CERT_LEN_PERIOD", random.randrange(4*60*60, 6*60*60)) + conf_dict.setdefault( + "GET_CERT_LEN_PERIOD", random.randrange(4 * 60 * 60, 6 * 60 * 60) + ) # max socket buffer size to the client direction, the more the faster, but more RAM hungry # can be the tuple (low, users_margin, high) for the adaptive case. If no much users, use high @@ -253,13 +278,13 @@ def init_config(): conf_dict.setdefault("TO_TG_BUFSIZE", 65536) # keepalive period for clients in secs - conf_dict.setdefault("CLIENT_KEEPALIVE", 10*60) + conf_dict.setdefault("CLIENT_KEEPALIVE", 10 * 60) # drop client after this timeout if the handshake fail conf_dict.setdefault("CLIENT_HANDSHAKE_TIMEOUT", random.randrange(5, 15)) # if client doesn't confirm data for this number of seconds, it is dropped - conf_dict.setdefault("CLIENT_ACK_TIMEOUT", 5*60) + conf_dict.setdefault("CLIENT_ACK_TIMEOUT", 5 * 60) # telegram servers connect timeout in seconds conf_dict.setdefault("TG_CONNECT_TIMEOUT", 10) @@ -299,9 +324,17 @@ def apply_upstream_proxy_settings(): # apply socks settings in place if config.SOCKS5_HOST and config.SOCKS5_PORT: import socks - print_err("Socket-proxy mode activated, it is incompatible with advertising and uvloop") - socks.set_default_proxy(socks.PROXY_TYPE_SOCKS5, config.SOCKS5_HOST, config.SOCKS5_PORT, - username=config.SOCKS5_USER, password=config.SOCKS5_PASS) + + print_err( + "Socket-proxy mode activated, it is incompatible with advertising and uvloop" + ) + socks.set_default_proxy( + socks.PROXY_TYPE_SOCKS5, + config.SOCKS5_HOST, + config.SOCKS5_PORT, + username=config.SOCKS5_USER, + password=config.SOCKS5_PASS, + ) if not hasattr(socket, "origsocket"): socket.origsocket = socket.socket socket.socket = socks.socksocket @@ -315,7 +348,7 @@ def try_use_cryptography_module(): from cryptography.hazmat.backends import default_backend class CryptographyEncryptorAdapter: - __slots__ = ('encryptor', 'decryptor') + __slots__ = ("encryptor", "decryptor") def __init__(self, cipher): self.encryptor = cipher.encryptor() @@ -361,7 +394,7 @@ def use_slow_bundled_cryptography_module(): print(msg, flush=True, file=sys.stderr) class BundledEncryptorAdapter: - __slots__ = ('mode', ) + __slots__ = ("mode",) def __init__(self, mode): self.mode = mode @@ -381,6 +414,7 @@ def create_aes_ctr(key, iv): def create_aes_cbc(key, iv): mode = pyaes.AESModeOfOperationCBC(key, iv) return BundledEncryptorAdapter(mode) + return create_aes_ctr, create_aes_cbc @@ -465,13 +499,13 @@ def __init__(self): def getrandbits(self, k): numbytes = (k + 7) // 8 - return int.from_bytes(self.getrandbytes(numbytes), 'big') >> (numbytes * 8 - k) + return int.from_bytes(self.getrandbytes(numbytes), "big") >> (numbytes * 8 - k) def getrandbytes(self, n): CHUNK_SIZE = 512 while n > len(self.buffer): - data = int.to_bytes(super().getrandbits(CHUNK_SIZE*8), CHUNK_SIZE, "big") + data = int.to_bytes(super().getrandbits(CHUNK_SIZE * 8), CHUNK_SIZE, "big") self.buffer += self.encryptor.encrypt(data) result = self.buffer[:n] @@ -490,22 +524,35 @@ def __init__(self): async def open_tg_connection(self, host, port, init_func=None): task = asyncio.open_connection(host, port, limit=get_to_clt_bufsize()) - reader_tgt, writer_tgt = await asyncio.wait_for(task, timeout=config.TG_CONNECT_TIMEOUT) + reader_tgt, writer_tgt = await asyncio.wait_for( + task, timeout=config.TG_CONNECT_TIMEOUT + ) set_keepalive(writer_tgt.get_extra_info("socket")) - set_bufsizes(writer_tgt.get_extra_info("socket"), get_to_clt_bufsize(), get_to_tg_bufsize()) + set_bufsizes( + writer_tgt.get_extra_info("socket"), + get_to_clt_bufsize(), + get_to_tg_bufsize(), + ) if init_func: - return await asyncio.wait_for(init_func(host, port, reader_tgt, writer_tgt), - timeout=config.TG_CONNECT_TIMEOUT) + return await asyncio.wait_for( + init_func(host, port, reader_tgt, writer_tgt), + timeout=config.TG_CONNECT_TIMEOUT, + ) return reader_tgt, writer_tgt def register_host_port(self, host, port, init_func): if (host, port, init_func) not in self.pools: self.pools[(host, port, init_func)] = [] - while len(self.pools[(host, port, init_func)]) < TgConnectionPool.MAX_CONNS_IN_POOL: - connect_task = asyncio.ensure_future(self.open_tg_connection(host, port, init_func)) + while ( + len(self.pools[(host, port, init_func)]) + < TgConnectionPool.MAX_CONNS_IN_POOL + ): + connect_task = asyncio.ensure_future( + self.open_tg_connection(host, port, init_func) + ) self.pools[(host, port, init_func)].append(connect_task) async def get_connection(self, host, port, init_func=None): @@ -537,7 +584,7 @@ async def get_connection(self, host, port, init_func=None): class LayeredStreamReaderBase: - __slots__ = ("upstream", ) + __slots__ = ("upstream",) def __init__(self, upstream): self.upstream = upstream @@ -550,7 +597,7 @@ async def readexactly(self, n): class LayeredStreamWriterBase: - __slots__ = ("upstream", ) + __slots__ = ("upstream",) def __init__(self, upstream): self.upstream = upstream @@ -579,7 +626,7 @@ def transport(self): class FakeTLSStreamReader(LayeredStreamReaderBase): - __slots__ = ('buf', ) + __slots__ = ("buf",) def __init__(self, upstream): self.upstream = upstream @@ -630,14 +677,14 @@ def __init__(self, upstream): def write(self, data, extra={}): MAX_CHUNK_SIZE = 16384 + 24 for start in range(0, len(data), MAX_CHUNK_SIZE): - end = min(start+MAX_CHUNK_SIZE, len(data)) - self.upstream.write(b"\x17\x03\x03" + int.to_bytes(end-start, 2, "big")) - self.upstream.write(data[start: end]) + end = min(start + MAX_CHUNK_SIZE, len(data)) + self.upstream.write(b"\x17\x03\x03" + int.to_bytes(end - start, 2, "big")) + self.upstream.write(data[start:end]) return len(data) class CryptoWrappedStreamReader(LayeredStreamReaderBase): - __slots__ = ('decryptor', 'block_size', 'buf') + __slots__ = ("decryptor", "block_size", "buf") def __init__(self, upstream, decryptor, block_size=1): self.upstream = upstream @@ -675,7 +722,7 @@ async def readexactly(self, n): class CryptoWrappedStreamWriter(LayeredStreamWriterBase): - __slots__ = ('encryptor', 'block_size') + __slots__ = ("encryptor", "block_size") def __init__(self, upstream, encryptor, block_size=1): self.upstream = upstream @@ -684,15 +731,17 @@ def __init__(self, upstream, encryptor, block_size=1): def write(self, data, extra={}): if len(data) % self.block_size != 0: - print_err("BUG: writing %d bytes not aligned to block size %d" % ( - len(data), self.block_size)) + print_err( + "BUG: writing %d bytes not aligned to block size %d" + % (len(data), self.block_size) + ) return 0 q = self.encryptor.encrypt(data) return self.upstream.write(q) class MTProtoFrameStreamReader(LayeredStreamReaderBase): - __slots__ = ('seq_no', ) + __slots__ = ("seq_no",) def __init__(self, upstream, seq_no=0): self.upstream = upstream @@ -706,7 +755,7 @@ async def read(self, buf_size): msg_len_bytes = await self.upstream.readexactly(4) msg_len = int.from_bytes(msg_len_bytes, "little") - len_is_bad = (msg_len % len(PADDING_FILLER) != 0) + len_is_bad = msg_len % len(PADDING_FILLER) != 0 if not MIN_MSG_LEN <= msg_len <= MAX_MSG_LEN or len_is_bad: print_err("msg_len is bad, closing connection", msg_len) return b"" @@ -730,7 +779,7 @@ async def read(self, buf_size): class MTProtoFrameStreamWriter(LayeredStreamWriterBase): - __slots__ = ('seq_no', ) + __slots__ = ("seq_no",) def __init__(self, upstream, seq_no=0): self.upstream = upstream @@ -745,7 +794,9 @@ def write(self, msg, extra={}): checksum = int.to_bytes(binascii.crc32(msg_without_checksum), 4, "little") full_msg = msg_without_checksum + checksum - padding = PADDING_FILLER * ((-len(full_msg) % CBC_PADDING) // len(PADDING_FILLER)) + padding = PADDING_FILLER * ( + (-len(full_msg) % CBC_PADDING) // len(PADDING_FILLER) + ) return self.upstream.write(full_msg + padding) @@ -762,7 +813,7 @@ async def read(self, buf_size): extra["QUICKACK_FLAG"] = True msg_len -= 0x80 - if msg_len == 0x7f: + if msg_len == 0x7F: msg_len_bytes = await self.upstream.readexactly(3) msg_len = int.from_bytes(msg_len_bytes, "little") @@ -777,11 +828,14 @@ class MTProtoCompactFrameStreamWriter(LayeredStreamWriterBase): __slots__ = () def write(self, data, extra={}): - SMALL_PKT_BORDER = 0x7f - LARGE_PKT_BORGER = 256 ** 3 + SMALL_PKT_BORDER = 0x7F + LARGE_PKT_BORGER = 256**3 if len(data) % 4 != 0: - print_err("BUG: MTProtoFrameStreamWriter attempted to send msg with len", len(data)) + print_err( + "BUG: MTProtoFrameStreamWriter attempted to send msg with len", + len(data), + ) return 0 if extra.get("SIMPLE_ACK"): @@ -792,7 +846,9 @@ def write(self, data, extra={}): if len_div_four < SMALL_PKT_BORDER: return self.upstream.write(bytes([len_div_four]) + data) elif len_div_four < LARGE_PKT_BORGER: - return self.upstream.write(b'\x7f' + int.to_bytes(len_div_four, 3, 'little') + data) + return self.upstream.write( + b"\x7f" + int.to_bytes(len_div_four, 3, "little") + data + ) else: print_err("Attempted to send too large pkt len =", len(data)) return 0 @@ -821,7 +877,7 @@ def write(self, data, extra={}): if extra.get("SIMPLE_ACK"): return self.upstream.write(data) else: - return self.upstream.write(int.to_bytes(len(data), 4, 'little') + data) + return self.upstream.write(int.to_bytes(len(data), 4, "little") + data) class MTProtoSecureIntermediateFrameStreamReader(LayeredStreamReaderBase): @@ -856,7 +912,7 @@ def write(self, data, extra={}): else: padding_len = myrandom.randrange(MAX_PADDING_LEN) padding = myrandom.getrandbytes(padding_len) - padded_data_len_bytes = int.to_bytes(len(data) + padding_len, 4, 'little') + padded_data_len_bytes = int.to_bytes(len(data) + padding_len, 4, "little") return self.upstream.write(padded_data_len_bytes + data + padding) @@ -867,7 +923,7 @@ async def read(self, msg): RPC_PROXY_ANS = b"\x0d\xda\x03\x44" RPC_CLOSE_EXT = b"\xa2\x34\xb6\x5e" RPC_SIMPLE_ACK = b"\x9b\x40\xac\x3b" - RPC_UNKNOWN = b'\xdf\xa2\x30\x57' + RPC_UNKNOWN = b"\xdf\xa2\x30\x57" data = await self.upstream.read(1) @@ -894,7 +950,7 @@ async def read(self, msg): class ProxyReqStreamWriter(LayeredStreamWriterBase): - __slots__ = ('remote_ip_port', 'our_ip_port', 'out_conn_id', 'proto_tag') + __slots__ = ("remote_ip_port", "our_ip_port", "out_conn_id", "proto_tag") def __init__(self, upstream, cl_ip, cl_port, my_ip, my_port, proto_tag): self.upstream = upstream @@ -978,7 +1034,9 @@ def set_keepalive(sock, interval=40, attempts=5): def set_ack_timeout(sock, timeout): if hasattr(socket, "TCP_USER_TIMEOUT"): - try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, timeout*1000) + try_setsockopt( + sock, socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, timeout * 1000 + ) def set_bufsizes(sock, recv_buf, send_buf): @@ -996,7 +1054,7 @@ def gen_x25519_public_key(): # generates some number which has square root by modulo P P = 2**255 - 19 n = myrandom.randrange(P) - return int.to_bytes((n*n) % P, length=32, byteorder="little") + return int.to_bytes((n * n) % P, length=32, byteorder="little") async def connect_reader_to_writer(reader, writer): @@ -1051,7 +1109,9 @@ async def handle_bad_client(reader_clt, writer_clt, handshake): 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) + 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() @@ -1062,7 +1122,7 @@ async def handle_bad_client(reader_clt, writer_clt, handshake): # if the server closed the connection with RST or FIN-RST, copy them to the client if not writer_srv.transport.is_closing(): # workaround for uvloop, it doesn't fire exceptions on write_eof - sock = writer_srv.get_extra_info('socket') + sock = writer_srv.get_extra_info("socket") raw_sock = socket.socket(sock.family, sock.type, sock.proto, sock.fileno()) try: raw_sock.shutdown(socket.SHUT_WR) @@ -1107,23 +1167,27 @@ async def handle_fake_tls_handshake(handshake, reader, writer, peer): tls_extensions = b"\x00\x2e" + b"\x00\x33\x00\x24" + b"\x00\x1d\x00\x20" tls_extensions += gen_x25519_public_key() + b"\x00\x2b\x00\x02\x03\x04" - digest = handshake[DIGEST_POS:DIGEST_POS+DIGEST_LEN] + digest = handshake[DIGEST_POS : DIGEST_POS + DIGEST_LEN] if digest[:DIGEST_HALFLEN] in used_handshakes: last_clients_with_same_handshake[peer[0]] += 1 return False sess_id_len = handshake[SESSION_ID_LEN_POS] - sess_id = handshake[SESSION_ID_POS:SESSION_ID_POS+sess_id_len] + sess_id = handshake[SESSION_ID_POS : SESSION_ID_POS + sess_id_len] for user in config.USERS: secret = bytes.fromhex(config.USERS[user]) - msg = handshake[:DIGEST_POS] + b"\x00"*DIGEST_LEN + handshake[DIGEST_POS+DIGEST_LEN:] + msg = ( + handshake[:DIGEST_POS] + + b"\x00" * DIGEST_LEN + + handshake[DIGEST_POS + DIGEST_LEN :] + ) computed_digest = hmac.new(secret, msg, digestmod=hashlib.sha256).digest() xored_digest = bytes(digest[i] ^ computed_digest[i] for i in range(DIGEST_LEN)) - digest_good = xored_digest.startswith(b"\x00" * (DIGEST_LEN-4)) + digest_good = xored_digest.startswith(b"\x00" * (DIGEST_LEN - 4)) if not digest_good: continue @@ -1132,8 +1196,10 @@ async def handle_fake_tls_handshake(handshake, reader, writer, peer): client_time_is_ok = TIME_SKEW_MIN < time.time() - timestamp < TIME_SKEW_MAX # some clients fail to read unix time and send the time since boot instead - client_time_is_small = timestamp < 60*60*24*1000 - accept_bad_time = config.IGNORE_TIME_SKEW or is_time_skewed or client_time_is_small + client_time_is_small = timestamp < 60 * 60 * 24 * 1000 + accept_bad_time = ( + config.IGNORE_TIME_SKEW or is_time_skewed or client_time_is_small + ) if not client_time_is_ok and not accept_bad_time: last_clients_with_time_skew[peer[0]] = (time.time() - timestamp) // 60 @@ -1141,7 +1207,7 @@ async def handle_fake_tls_handshake(handshake, reader, writer, peer): http_data = myrandom.getrandbytes(fake_cert_len) - srv_hello = TLS_VERS + b"\x00"*DIGEST_LEN + bytes([sess_id_len]) + sess_id + srv_hello = TLS_VERS + b"\x00" * DIGEST_LEN + bytes([sess_id_len]) + sess_id srv_hello += TLS_CIPHERSUITE + b"\x00" + tls_extensions hello_pkt = b"\x16" + TLS_VERS + int.to_bytes(len(srv_hello) + 4, 2, "big") @@ -1149,8 +1215,14 @@ async def handle_fake_tls_handshake(handshake, reader, writer, peer): hello_pkt += TLS_CHANGE_CIPHER + TLS_APP_HTTP2_HDR hello_pkt += int.to_bytes(len(http_data), 2, "big") + http_data - computed_digest = hmac.new(secret, msg=digest+hello_pkt, digestmod=hashlib.sha256).digest() - hello_pkt = hello_pkt[:DIGEST_POS] + computed_digest + hello_pkt[DIGEST_POS+DIGEST_LEN:] + computed_digest = hmac.new( + secret, msg=digest + hello_pkt, digestmod=hashlib.sha256 + ).digest() + hello_pkt = ( + hello_pkt[:DIGEST_POS] + + computed_digest + + hello_pkt[DIGEST_POS + DIGEST_LEN :] + ) writer.write(hello_pkt) await writer.drain() @@ -1194,8 +1266,8 @@ async def handle_proxy_protocol(reader, peer=None): _, proxy_fam, *proxy_addr = header[:-2].split(b" ") if proxy_fam in (PROXY_TCP4, PROXY_TCP6): if len(proxy_addr) == 4: - src_addr = proxy_addr[0].decode('ascii') - src_port = int(proxy_addr[2].decode('ascii')) + src_addr = proxy_addr[0].decode("ascii") + src_port = int(proxy_addr[2].decode("ascii")) return (src_addr, src_port) elif proxy_fam == PROXY_UNKNOWN: return peer @@ -1205,19 +1277,19 @@ async def handle_proxy_protocol(reader, peer=None): if header.startswith(PROXY2_SIGNATURE): # proxy header v2 proxy_ver = header[12] - if proxy_ver & 0xf0 != 0x20: + if proxy_ver & 0xF0 != 0x20: return False proxy_len = int.from_bytes(header[14:16], "big") proxy_addr = await reader.readexactly(proxy_len) if proxy_ver == 0x21: proxy_fam = header[13] >> 4 if proxy_fam == PROXY2_AF_INET: - if proxy_len >= (4 + 2)*2: + if proxy_len >= (4 + 2) * 2: src_addr = socket.inet_ntop(socket.AF_INET, proxy_addr[:4]) src_port = int.from_bytes(proxy_addr[8:10], "big") return (src_addr, src_port) elif proxy_fam == PROXY2_AF_INET6: - if proxy_len >= (16 + 2)*2: + if proxy_len >= (16 + 2) * 2: src_addr = socket.inet_ntop(socket.AF_INET6, proxy_addr[:16]) src_port = int.from_bytes(proxy_addr[32:34], "big") return (src_addr, src_port) @@ -1268,7 +1340,9 @@ async def handle_handshake(reader, writer): if is_tls_handshake: handshake += await reader.readexactly(tls_handshake_len) - tls_handshake_result = await handle_fake_tls_handshake(handshake, reader, writer, peer) + tls_handshake_result = await handle_fake_tls_handshake( + handshake, reader, writer, peer + ) if not tls_handshake_result: await handle_bad_client(reader, writer, handshake) @@ -1281,9 +1355,9 @@ async def handle_handshake(reader, writer): return False handshake += await reader.readexactly(HANDSHAKE_LEN - len(handshake)) - dec_prekey_and_iv = handshake[SKIP_LEN:SKIP_LEN+PREKEY_LEN+IV_LEN] + dec_prekey_and_iv = handshake[SKIP_LEN : SKIP_LEN + PREKEY_LEN + IV_LEN] dec_prekey, dec_iv = dec_prekey_and_iv[:PREKEY_LEN], dec_prekey_and_iv[PREKEY_LEN:] - enc_prekey_and_iv = handshake[SKIP_LEN:SKIP_LEN+PREKEY_LEN+IV_LEN][::-1] + enc_prekey_and_iv = handshake[SKIP_LEN : SKIP_LEN + PREKEY_LEN + IV_LEN][::-1] enc_prekey, enc_iv = enc_prekey_and_iv[:PREKEY_LEN], enc_prekey_and_iv[PREKEY_LEN:] if dec_prekey_and_iv in used_handshakes: @@ -1302,8 +1376,12 @@ async def handle_handshake(reader, writer): decrypted = decryptor.decrypt(handshake) - proto_tag = decrypted[PROTO_TAG_POS:PROTO_TAG_POS+4] - if proto_tag not in (PROTO_TAG_ABRIDGED, PROTO_TAG_INTERMEDIATE, PROTO_TAG_SECURE): + proto_tag = decrypted[PROTO_TAG_POS : PROTO_TAG_POS + 4] + if proto_tag not in ( + PROTO_TAG_ABRIDGED, + PROTO_TAG_INTERMEDIATE, + PROTO_TAG_SECURE, + ): continue if proto_tag == PROTO_TAG_SECURE: @@ -1315,7 +1393,9 @@ async def handle_handshake(reader, writer): if not config.MODES["classic"]: continue - dc_idx = int.from_bytes(decrypted[DC_IDX_POS:DC_IDX_POS+2], "little", signed=True) + dc_idx = int.from_bytes( + decrypted[DC_IDX_POS : DC_IDX_POS + 2], "little", signed=True + ) if config.REPLAY_CHECK_LEN > 0: while len(used_handshakes) >= config.REPLAY_CHECK_LEN: @@ -1339,9 +1419,14 @@ async def handle_handshake(reader, writer): async def do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=None): RESERVED_NONCE_FIRST_CHARS = [b"\xef"] - RESERVED_NONCE_BEGININGS = [b"\x48\x45\x41\x44", b"\x50\x4F\x53\x54", - b"\x47\x45\x54\x20", b"\xee\xee\xee\xee", - b"\xdd\xdd\xdd\xdd", b"\x16\x03\x01\x02"] + RESERVED_NONCE_BEGININGS = [ + b"\x48\x45\x41\x44", + b"\x50\x4f\x53\x54", + b"\x47\x45\x54\x20", + b"\xee\xee\xee\xee", + b"\xdd\xdd\xdd\xdd", + b"\x16\x03\x01\x02", + ] RESERVED_NONCE_CONTINUES = [b"\x00\x00\x00\x00"] global my_ip_info @@ -1359,12 +1444,19 @@ async def do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=None): dc = TG_DATACENTERS_V4[dc_idx] try: - reader_tgt, writer_tgt = await tg_connection_pool.get_connection(dc, TG_DATACENTER_PORT) + reader_tgt, writer_tgt = await tg_connection_pool.get_connection( + dc, TG_DATACENTER_PORT + ) except ConnectionRefusedError as E: - print_err("Got connection refused while trying to connect to", dc, TG_DATACENTER_PORT) + print_err( + "Got connection refused while trying to connect to", dc, TG_DATACENTER_PORT + ) return False except ConnectionAbortedError as E: - print_err("The Telegram server connection is bad: %d (%s %s) %s" % (dc_idx, addr, port, E)) + print_err( + "The Telegram server connection is bad: %d (%s %s) %s" + % (dc_idx, addr, port, E) + ) return False except (OSError, asyncio.TimeoutError) as E: print_err("Unable to connect to", dc, TG_DATACENTER_PORT) @@ -1380,18 +1472,18 @@ async def do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=None): continue break - rnd[PROTO_TAG_POS:PROTO_TAG_POS+4] = proto_tag + rnd[PROTO_TAG_POS : PROTO_TAG_POS + 4] = proto_tag if dec_key_and_iv: - rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN] = dec_key_and_iv[::-1] + rnd[SKIP_LEN : SKIP_LEN + KEY_LEN + IV_LEN] = dec_key_and_iv[::-1] rnd = bytes(rnd) - dec_key_and_iv = rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN][::-1] + dec_key_and_iv = rnd[SKIP_LEN : SKIP_LEN + KEY_LEN + IV_LEN][::-1] dec_key, dec_iv = dec_key_and_iv[:KEY_LEN], dec_key_and_iv[KEY_LEN:] decryptor = create_aes_ctr(key=dec_key, iv=int.from_bytes(dec_iv, "big")) - enc_key_and_iv = rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN] + enc_key_and_iv = rnd[SKIP_LEN : SKIP_LEN + KEY_LEN + IV_LEN] enc_key, enc_iv = enc_key_and_iv[:KEY_LEN], enc_key_and_iv[KEY_LEN:] encryptor = create_aes_ctr(key=enc_key, iv=int.from_bytes(enc_iv, "big")) @@ -1406,9 +1498,19 @@ async def do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=None): return reader_tgt, writer_tgt -def get_middleproxy_aes_key_and_iv(nonce_srv, nonce_clt, clt_ts, srv_ip, clt_port, purpose, - clt_ip, srv_port, middleproxy_secret, clt_ipv6=None, - srv_ipv6=None): +def get_middleproxy_aes_key_and_iv( + nonce_srv, + nonce_clt, + clt_ts, + srv_ip, + clt_port, + purpose, + clt_ip, + srv_port, + middleproxy_secret, + clt_ipv6=None, + srv_ipv6=None, +): EMPTY_IP = b"\x00\x00\x00\x00" if not clt_ip or not srv_ip: @@ -1416,7 +1518,9 @@ def get_middleproxy_aes_key_and_iv(nonce_srv, nonce_clt, clt_ts, srv_ip, clt_por srv_ip = EMPTY_IP s = bytearray() - s += nonce_srv + nonce_clt + clt_ts + srv_ip + clt_port + purpose + clt_ip + srv_port + s += ( + nonce_srv + nonce_clt + clt_ts + srv_ip + clt_port + purpose + clt_ip + srv_port + ) s += middleproxy_secret + nonce_srv if clt_ipv6 and srv_ipv6: @@ -1433,7 +1537,7 @@ def get_middleproxy_aes_key_and_iv(nonce_srv, nonce_clt, clt_ts, srv_ip, clt_por async def middleproxy_handshake(host, port, reader_tgt, writer_tgt): - """ The most logic of middleproxy handshake, launched in pool """ + """The most logic of middleproxy handshake, launched in pool""" START_SEQ_NO = -2 NONCE_LEN = 16 @@ -1464,17 +1568,25 @@ async def middleproxy_handshake(host, port, reader_tgt, writer_tgt): raise ConnectionAbortedError("bad rpc answer length") rpc_type, rpc_key_selector, rpc_schema, rpc_crypto_ts, rpc_nonce = ( - ans[:4], ans[4:8], ans[8:12], ans[12:16], ans[16:32] + ans[:4], + ans[4:8], + ans[8:12], + ans[12:16], + ans[16:32], ) - if rpc_type != RPC_NONCE or rpc_key_selector != key_selector or rpc_schema != CRYPTO_AES: + if ( + rpc_type != RPC_NONCE + or rpc_key_selector != key_selector + or rpc_schema != CRYPTO_AES + ): raise ConnectionAbortedError("bad rpc answer") # get keys - tg_ip, tg_port = writer_tgt.upstream.get_extra_info('peername')[:2] - my_ip, my_port = writer_tgt.upstream.get_extra_info('sockname')[:2] + tg_ip, tg_port = writer_tgt.upstream.get_extra_info("peername")[:2] + my_ip, my_port = writer_tgt.upstream.get_extra_info("sockname")[:2] - use_ipv6_tg = (":" in tg_ip) + use_ipv6_tg = ":" in tg_ip if not use_ipv6_tg: if my_ip_info["ipv4"]: @@ -1500,14 +1612,32 @@ async def middleproxy_handshake(host, port, reader_tgt, writer_tgt): my_port_bytes = int.to_bytes(my_port, 2, "little") enc_key, enc_iv = get_middleproxy_aes_key_and_iv( - nonce_srv=rpc_nonce, nonce_clt=nonce, clt_ts=crypto_ts, srv_ip=tg_ip_bytes, - clt_port=my_port_bytes, purpose=b"CLIENT", clt_ip=my_ip_bytes, srv_port=tg_port_bytes, - middleproxy_secret=PROXY_SECRET, clt_ipv6=my_ipv6_bytes, srv_ipv6=tg_ipv6_bytes) + nonce_srv=rpc_nonce, + nonce_clt=nonce, + clt_ts=crypto_ts, + srv_ip=tg_ip_bytes, + clt_port=my_port_bytes, + purpose=b"CLIENT", + clt_ip=my_ip_bytes, + srv_port=tg_port_bytes, + middleproxy_secret=PROXY_SECRET, + clt_ipv6=my_ipv6_bytes, + srv_ipv6=tg_ipv6_bytes, + ) dec_key, dec_iv = get_middleproxy_aes_key_and_iv( - nonce_srv=rpc_nonce, nonce_clt=nonce, clt_ts=crypto_ts, srv_ip=tg_ip_bytes, - clt_port=my_port_bytes, purpose=b"SERVER", clt_ip=my_ip_bytes, srv_port=tg_port_bytes, - middleproxy_secret=PROXY_SECRET, clt_ipv6=my_ipv6_bytes, srv_ipv6=tg_ipv6_bytes) + nonce_srv=rpc_nonce, + nonce_clt=nonce, + clt_ts=crypto_ts, + srv_ip=tg_ip_bytes, + clt_port=my_port_bytes, + purpose=b"SERVER", + clt_ip=my_ip_bytes, + srv_port=tg_port_bytes, + middleproxy_secret=PROXY_SECRET, + clt_ipv6=my_ipv6_bytes, + srv_ipv6=tg_ipv6_bytes, + ) encryptor = create_aes_cbc(key=enc_key, iv=enc_iv) decryptor = create_aes_cbc(key=dec_key, iv=dec_iv) @@ -1518,18 +1648,26 @@ async def middleproxy_handshake(host, port, reader_tgt, writer_tgt): # TODO: pass client ip and port here for statistics handshake = RPC_HANDSHAKE + RPC_FLAGS + SENDER_PID + PEER_PID - writer_tgt.upstream = CryptoWrappedStreamWriter(writer_tgt.upstream, encryptor, block_size=16) + writer_tgt.upstream = CryptoWrappedStreamWriter( + writer_tgt.upstream, encryptor, block_size=16 + ) writer_tgt.write(handshake) await writer_tgt.drain() - reader_tgt.upstream = CryptoWrappedStreamReader(reader_tgt.upstream, decryptor, block_size=16) + reader_tgt.upstream = CryptoWrappedStreamReader( + reader_tgt.upstream, decryptor, block_size=16 + ) handshake_ans = await reader_tgt.read(1) if len(handshake_ans) != RPC_HANDSHAKE_ANS_LEN: raise ConnectionAbortedError("bad rpc handshake answer length") handshake_type, handshake_flags, handshake_sender_pid, handshake_peer_pid = ( - handshake_ans[:4], handshake_ans[4:8], handshake_ans[8:20], handshake_ans[20:32]) + handshake_ans[:4], + handshake_ans[4:8], + handshake_ans[8:20], + handshake_ans[20:32], + ) if handshake_type != RPC_HANDSHAKE or handshake_peer_pid != SENDER_PID: raise ConnectionAbortedError("bad rpc handshake answer") @@ -1540,7 +1678,7 @@ async def do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port): global my_ip_info global tg_connection_pool - use_ipv6_tg = (my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"])) + use_ipv6_tg = my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"]) if use_ipv6_tg: if dc_idx not in TG_MIDDLE_PROXIES_V6: @@ -1555,16 +1693,26 @@ async def do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port): ret = await tg_connection_pool.get_connection(addr, port, middleproxy_handshake) reader_tgt, writer_tgt, my_ip, my_port = ret except ConnectionRefusedError as E: - print_err("The Telegram server %d (%s %s) is refusing connections" % (dc_idx, addr, port)) + print_err( + "The Telegram server %d (%s %s) is refusing connections" + % (dc_idx, addr, port) + ) return False except ConnectionAbortedError as E: - print_err("The Telegram server connection is bad: %d (%s %s) %s" % (dc_idx, addr, port, E)) + print_err( + "The Telegram server connection is bad: %d (%s %s) %s" + % (dc_idx, addr, port, E) + ) return False except (OSError, asyncio.TimeoutError) as E: - print_err("Unable to connect to the Telegram server %d (%s %s)" % (dc_idx, addr, port)) + print_err( + "Unable to connect to the Telegram server %d (%s %s)" % (dc_idx, addr, port) + ) return False - writer_tgt = ProxyReqStreamWriter(writer_tgt, cl_ip, cl_port, my_ip, my_port, proto_tag) + writer_tgt = ProxyReqStreamWriter( + writer_tgt, cl_ip, cl_port, my_ip, my_port, proto_tag + ) reader_tgt = ProxyReqStreamReader(reader_tgt) return reader_tgt, writer_tgt @@ -1588,9 +1736,13 @@ async def tg_connect_reader_to_writer(rd, wr, user, rd_buf_size, is_upstream): return else: if is_upstream: - update_user_stats(user, octets_from_client=len(data), msgs_from_client=1) + update_user_stats( + user, octets_from_client=len(data), msgs_from_client=1 + ) else: - update_user_stats(user, octets_to_client=len(data), msgs_to_client=1) + update_user_stats( + user, octets_to_client=len(data), msgs_to_client=1 + ) wr.write(data, extra) await wr.drain() @@ -1600,15 +1752,21 @@ async def tg_connect_reader_to_writer(rd, wr, user, rd_buf_size, is_upstream): async def handle_client(reader_clt, writer_clt): - set_keepalive(writer_clt.get_extra_info("socket"), config.CLIENT_KEEPALIVE, attempts=3) + set_keepalive( + writer_clt.get_extra_info("socket"), config.CLIENT_KEEPALIVE, attempts=3 + ) set_ack_timeout(writer_clt.get_extra_info("socket"), config.CLIENT_ACK_TIMEOUT) - set_bufsizes(writer_clt.get_extra_info("socket"), get_to_tg_bufsize(), get_to_clt_bufsize()) + set_bufsizes( + writer_clt.get_extra_info("socket"), get_to_tg_bufsize(), get_to_clt_bufsize() + ) update_stats(connects_all=1) try: - clt_data = await asyncio.wait_for(handle_handshake(reader_clt, writer_clt), - timeout=config.CLIENT_HANDSHAKE_TIMEOUT) + clt_data = await asyncio.wait_for( + handle_handshake(reader_clt, writer_clt), + timeout=config.CLIENT_HANDSHAKE_TIMEOUT, + ) except asyncio.TimeoutError: update_stats(handshake_timeouts=1) return @@ -1621,11 +1779,13 @@ async def handle_client(reader_clt, writer_clt): update_user_stats(user, connects=1) - connect_directly = (not config.USE_MIDDLE_PROXY or disable_middle_proxy) + connect_directly = not config.USE_MIDDLE_PROXY or disable_middle_proxy if connect_directly: if config.FAST_MODE: - tg_data = await do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=enc_key_and_iv) + tg_data = await do_direct_handshake( + proto_tag, dc_idx, dec_key_and_iv=enc_key_and_iv + ) else: tg_data = await do_direct_handshake(proto_tag, dc_idx) else: @@ -1637,6 +1797,7 @@ async def handle_client(reader_clt, writer_clt): reader_tg, writer_tg = tg_data if connect_directly and config.FAST_MODE: + class FakeEncryptor: def encrypt(self, data): return data @@ -1661,34 +1822,37 @@ def decrypt(self, data): else: return - tg_to_clt = tg_connect_reader_to_writer(reader_tg, writer_clt, user, - get_to_clt_bufsize(), False) - clt_to_tg = tg_connect_reader_to_writer(reader_clt, writer_tg, - user, get_to_tg_bufsize(), True) + tg_to_clt = tg_connect_reader_to_writer( + reader_tg, writer_clt, user, get_to_clt_bufsize(), False + ) + clt_to_tg = tg_connect_reader_to_writer( + reader_clt, writer_tg, user, get_to_tg_bufsize(), True + ) task_tg_to_clt = asyncio.ensure_future(tg_to_clt) task_clt_to_tg = asyncio.ensure_future(clt_to_tg) update_user_stats(user, curr_connects=1) tcp_limit_hit = ( - user in config.USER_MAX_TCP_CONNS and - user_stats[user]["curr_connects"] > config.USER_MAX_TCP_CONNS[user] + user in config.USER_MAX_TCP_CONNS + and user_stats[user]["curr_connects"] > config.USER_MAX_TCP_CONNS[user] ) user_expired = ( - user in config.USER_EXPIRATIONS and - datetime.datetime.now() > config.USER_EXPIRATIONS[user] + user in config.USER_EXPIRATIONS + and datetime.datetime.now() > config.USER_EXPIRATIONS[user] ) - user_data_quota_hit = ( - user in config.USER_DATA_QUOTA and - (user_stats[user]["octets_to_client"] + - user_stats[user]["octets_from_client"] > config.USER_DATA_QUOTA[user]) + user_data_quota_hit = user in config.USER_DATA_QUOTA and ( + user_stats[user]["octets_to_client"] + user_stats[user]["octets_from_client"] + > config.USER_DATA_QUOTA[user] ) if (not tcp_limit_hit) and (not user_expired) and (not user_data_quota_hit): start = time.time() - await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED) + await asyncio.wait( + [task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED + ) update_durations(time.time() - start) update_user_stats(user, curr_connects=-1) @@ -1728,7 +1892,7 @@ def make_metrics_pkt(metrics): for tag, tag_val in val.items(): if tag == "val": continue - tag_val = tag_val.replace('"', r'\"') + tag_val = tag_val.replace('"', r"\"") tags.append('%s="%s"' % (tag, tag_val)) pkt_body_list.append("%s{%s} %s" % (name, ",".join(tags), val["val"])) else: @@ -1740,7 +1904,9 @@ def make_metrics_pkt(metrics): pkt_header_list.append("Connection: close") pkt_header_list.append("Content-Length: %d" % len(pkt_body)) pkt_header_list.append("Content-Type: text/plain; version=0.0.4; charset=utf-8") - pkt_header_list.append("Date: %s" % time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())) + pkt_header_list.append( + "Date: %s" % time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) + ) pkt_header = "\r\n".join(pkt_header_list) @@ -1764,38 +1930,75 @@ async def handle_metrics(reader, writer): try: metrics = [] - metrics.append(["uptime", "counter", "proxy uptime", time.time() - proxy_start_time]) - metrics.append(["connects_bad", "counter", "connects with bad secret", - stats["connects_bad"]]) - metrics.append(["connects_all", "counter", "incoming connects", stats["connects_all"]]) - metrics.append(["handshake_timeouts", "counter", "number of timed out handshakes", - stats["handshake_timeouts"]]) + metrics.append( + ["uptime", "counter", "proxy uptime", time.time() - proxy_start_time] + ) + metrics.append( + [ + "connects_bad", + "counter", + "connects with bad secret", + stats["connects_bad"], + ] + ) + metrics.append( + ["connects_all", "counter", "incoming connects", stats["connects_all"]] + ) + metrics.append( + [ + "handshake_timeouts", + "counter", + "number of timed out handshakes", + stats["handshake_timeouts"], + ] + ) if config.METRICS_EXPORT_LINKS: for link in proxy_links: link_as_metric = link.copy() link_as_metric["val"] = 1 - metrics.append(["proxy_link_info", "counter", - "the proxy link info", link_as_metric]) + metrics.append( + [ + "proxy_link_info", + "counter", + "the proxy link info", + link_as_metric, + ] + ) bucket_start = 0 for bucket in STAT_DURATION_BUCKETS: bucket_end = bucket if bucket != STAT_DURATION_BUCKETS[-1] else "+Inf" metric = { "bucket": "%s-%s" % (bucket_start, bucket_end), - "val": stats["connects_with_duration_le_%s" % str(bucket)] + "val": stats["connects_with_duration_le_%s" % str(bucket)], } - metrics.append(["connects_by_duration", "counter", "connects by duration", metric]) + metrics.append( + ["connects_by_duration", "counter", "connects by duration", metric] + ) bucket_start = bucket_end user_metrics_desc = [ ["user_connects", "counter", "user connects", "connects"], ["user_connects_curr", "gauge", "current user connects", "curr_connects"], - ["user_octets", "counter", "octets proxied for user", - "octets_from_client+octets_to_client"], - ["user_msgs", "counter", "msgs proxied for user", - "msgs_from_client+msgs_to_client"], - ["user_octets_from", "counter", "octets proxied from user", "octets_from_client"], + [ + "user_octets", + "counter", + "octets proxied for user", + "octets_from_client+octets_to_client", + ], + [ + "user_msgs", + "counter", + "msgs proxied for user", + "msgs_from_client+msgs_to_client", + ], + [ + "user_octets_from", + "counter", + "octets proxied from user", + "octets_from_client", + ], ["user_octets_to", "counter", "octets proxied to user", "octets_to_client"], ["user_msgs_from", "counter", "msgs proxied from user", "msgs_from_client"], ["user_msgs_to", "counter", "msgs proxied to user", "msgs_to_client"], @@ -1833,10 +2036,16 @@ async def stats_printer(): print("Stats for", time.strftime("%d.%m.%Y %H:%M:%S")) for user, stat in user_stats.items(): - print("%s: %d connects (%d current), %.2f MB, %d msgs" % ( - user, stat["connects"], stat["curr_connects"], - (stat["octets_from_client"] + stat["octets_to_client"]) / 1000000, - stat["msgs_from_client"] + stat["msgs_to_client"])) + print( + "%s: %d connects (%d current), %.2f MB, %d msgs" + % ( + user, + stat["connects"], + stat["curr_connects"], + (stat["octets_from_client"] + stat["octets_to_client"]) / 1000000, + stat["msgs_from_client"] + stat["msgs_to_client"], + ) + ) print(flush=True) if last_client_ips: @@ -1861,12 +2070,13 @@ async def stats_printer(): async def make_https_req(url, host="core.telegram.org"): - """ Make request, return resp body and headers. """ + """Make request, return resp body and headers.""" SSL_PORT = 443 url_data = urllib.parse.urlparse(url) - HTTP_REQ_TEMPLATE = "\r\n".join(["GET %s HTTP/1.1", "Host: %s", - "Connection: close"]) + "\r\n\r\n" + HTTP_REQ_TEMPLATE = ( + "\r\n".join(["GET %s HTTP/1.1", "Host: %s", "Connection: close"]) + "\r\n\r\n" + ) reader, writer = await asyncio.open_connection(url_data.netloc, SSL_PORT, ssl=True) req = HTTP_REQ_TEMPLATE % (urllib.parse.quote(url_data.path), host) writer.write(req.encode("utf8")) @@ -1934,8 +2144,10 @@ async def get_tls_record(reader): record4_type, record4 = await get_tls_record(reader) if record4_type != 23: return b"" - msg = ("The MASK_HOST %s sent some TLS record before certificate record, this makes the " + - "proxy more detectable") % config.MASK_HOST + msg = ( + "The MASK_HOST %s sent some TLS record before certificate record, this makes the " + + "proxy more detectable" + ) % config.MASK_HOST print_err(msg) return record4 @@ -1956,29 +2168,40 @@ async def get_mask_host_cert_len(): await asyncio.sleep(MASK_ENABLING_CHECK_PERIOD) continue - task = get_encrypted_cert(config.MASK_HOST, config.MASK_PORT, config.TLS_DOMAIN) + task = get_encrypted_cert( + config.MASK_HOST, config.MASK_PORT, config.TLS_DOMAIN + ) cert = await asyncio.wait_for(task, timeout=GET_CERT_TIMEOUT) if cert: if len(cert) < MIN_CERT_LEN: - msg = ("The MASK_HOST %s returned several TLS records, this is not supported" % - config.MASK_HOST) + msg = ( + "The MASK_HOST %s returned several TLS records, this is not supported" + % config.MASK_HOST + ) print_err(msg) elif len(cert) != fake_cert_len: fake_cert_len = len(cert) - print_err("Got cert from the MASK_HOST %s, its length is %d" % - (config.MASK_HOST, fake_cert_len)) + print_err( + "Got cert from the MASK_HOST %s, its length is %d" + % (config.MASK_HOST, fake_cert_len) + ) else: - print_err("The MASK_HOST %s is not TLS 1.3 host, this is not recommended" % - config.MASK_HOST) + print_err( + "The MASK_HOST %s is not TLS 1.3 host, this is not recommended" + % config.MASK_HOST + ) except ConnectionRefusedError: - print_err("The MASK_HOST %s is refusing connections, this is not recommended" % - config.MASK_HOST) + print_err( + "The MASK_HOST %s is refusing connections, this is not recommended" + % config.MASK_HOST + ) except (TimeoutError, asyncio.TimeoutError): - print_err("Got timeout while getting TLS handshake from MASK_HOST %s" % - config.MASK_HOST) + print_err( + "Got timeout while getting TLS handshake from MASK_HOST %s" + % config.MASK_HOST + ) except Exception as E: - print_err("Failed to connect to MASK_HOST %s: %s" % ( - config.MASK_HOST, E)) + print_err("Failed to connect to MASK_HOST %s: %s" % (config.MASK_HOST, E)) await asyncio.sleep(config.GET_CERT_LEN_PERIOD) @@ -1998,11 +2221,15 @@ async def get_srv_time(): for line in headers.split(b"\r\n"): if not line.startswith(b"Date: "): continue - line = line[len("Date: "):].decode() + line = line[len("Date: ") :].decode() srv_time = datetime.datetime.strptime(line, "%a, %d %b %Y %H:%M:%S %Z") now_time = datetime.datetime.utcnow() - is_time_skewed = (now_time-srv_time).total_seconds() > MAX_TIME_SKEW - if is_time_skewed and config.USE_MIDDLE_PROXY and not disable_middle_proxy: + is_time_skewed = (now_time - srv_time).total_seconds() > MAX_TIME_SKEW + if ( + is_time_skewed + and config.USE_MIDDLE_PROXY + and not disable_middle_proxy + ): print_err("Time skew detected, please set the clock") print_err("Server time:", srv_time, "your time:", now_time) print_err("Disabling advertising to continue serving") @@ -2130,7 +2357,9 @@ def print_tg_info(): if config.PORT == 3256: print("The default port 3256 is used, this is not recommended", flush=True) if not config.MODES["classic"] and not config.MODES["secure"]: - print("Since you have TLS only mode enabled the best port is 443", flush=True) + print( + "Since you have TLS only mode enabled the best port is 443", flush=True + ) print_default_warning = True if not config.MY_DOMAIN: @@ -2146,14 +2375,14 @@ def print_tg_info(): for ip in ip_addrs: if config.MODES["classic"]: params = {"server": ip, "port": config.PORT, "secret": secret} - params_encodeded = urllib.parse.urlencode(params, safe=':') + params_encodeded = urllib.parse.urlencode(params, safe=":") classic_link = "tg://proxy?{}".format(params_encodeded) proxy_links.append({"user": user, "link": classic_link}) print("{}: {}".format(user, classic_link), flush=True) if config.MODES["secure"]: params = {"server": ip, "port": config.PORT, "secret": "dd" + secret} - params_encodeded = urllib.parse.urlencode(params, safe=':') + params_encodeded = urllib.parse.urlencode(params, safe=":") dd_link = "tg://proxy?{}".format(params_encodeded) proxy_links.append({"user": user, "link": dd_link}) print("{}: {}".format(user, dd_link), flush=True) @@ -2164,21 +2393,31 @@ def print_tg_info(): # tls_secret = bytes.fromhex("ee" + secret) + config.TLS_DOMAIN.encode() # tls_secret_base64 = base64.urlsafe_b64encode(tls_secret) params = {"server": ip, "port": config.PORT, "secret": tls_secret} - params_encodeded = urllib.parse.urlencode(params, safe=':') + params_encodeded = urllib.parse.urlencode(params, safe=":") tls_link = "tg://proxy?{}".format(params_encodeded) proxy_links.append({"user": user, "link": tls_link}) print("{}: {}".format(user, tls_link), flush=True) - if secret in ["00000000000000000000000000000000", "0123456789abcdef0123456789abcdef", - "00000000000000000000000000000001"]: - msg = "The default secret {} is used, this is not recommended".format(secret) + if secret in [ + "00000000000000000000000000000000", + "0123456789abcdef0123456789abcdef", + "00000000000000000000000000000001", + ]: + msg = "The default secret {} is used, this is not recommended".format( + secret + ) print(msg, flush=True) - random_secret = "".join(myrandom.choice("0123456789abcdef") for i in range(32)) + random_secret = "".join( + myrandom.choice("0123456789abcdef") for i in range(32) + ) print("You can change it to this random secret:", random_secret, flush=True) print_default_warning = True if config.TLS_DOMAIN == "www.google.com": - print("The default TLS_DOMAIN www.google.com is used, this is not recommended", flush=True) + print( + "The default TLS_DOMAIN www.google.com is used, this is not recommended", + flush=True, + ) msg = "You should use random existing domain instead, bad clients are proxied there" print(msg, flush=True) print_default_warning = True @@ -2190,10 +2429,13 @@ def print_tg_info(): def setup_files_limit(): try: import resource + soft_fd_limit, hard_fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE) resource.setrlimit(resource.RLIMIT_NOFILE, (hard_fd_limit, hard_fd_limit)) except (ValueError, OSError): - print("Failed to increase the limit of opened files", flush=True, file=sys.stderr) + print( + "Failed to increase the limit of opened files", flush=True, file=sys.stderr + ) except ImportError: pass @@ -2204,14 +2446,17 @@ def setup_asyncio(): def setup_signals(): - if hasattr(signal, 'SIGUSR1'): + if hasattr(signal, "SIGUSR1"): + def debug_signal(signum, frame): import pdb + pdb.set_trace() signal.signal(signal.SIGUSR1, debug_signal) - if hasattr(signal, 'SIGUSR2'): + if hasattr(signal, "SIGUSR2"): + def reload_signal(signum, frame): init_config() ensure_users_in_user_stats() @@ -2228,6 +2473,7 @@ def try_setup_uvloop(): return try: import uvloop + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) print_err("Found uvloop, using it for optimal performance") except ImportError: @@ -2253,12 +2499,11 @@ def loop_exception_handler(loop, context): if isinstance(exception, OSError): IGNORE_ERRNO = { 10038, # operation on non-socket on Windows, likely because fd == -1 - 121, # the semaphore timeout period has expired on Windows + 121, # the semaphore timeout period has expired on Windows } FORCE_CLOSE_ERRNO = { - 113, # no route to host - + 113, # no route to host } if exception.errno in IGNORE_ERRNO: return @@ -2277,30 +2522,43 @@ def create_servers(loop): has_unix = hasattr(socket, "AF_UNIX") if config.LISTEN_ADDR_IPV4: - task = asyncio.start_server(handle_client_wrapper, config.LISTEN_ADDR_IPV4, config.PORT, - limit=get_to_tg_bufsize(), reuse_port=reuse_port) + task = asyncio.start_server( + handle_client_wrapper, + config.LISTEN_ADDR_IPV4, + config.PORT, + limit=get_to_tg_bufsize(), + reuse_port=reuse_port, + ) servers.append(loop.run_until_complete(task)) if config.LISTEN_ADDR_IPV6 and socket.has_ipv6: - task = asyncio.start_server(handle_client_wrapper, config.LISTEN_ADDR_IPV6, config.PORT, - limit=get_to_tg_bufsize(), reuse_port=reuse_port) + task = asyncio.start_server( + handle_client_wrapper, + config.LISTEN_ADDR_IPV6, + config.PORT, + limit=get_to_tg_bufsize(), + reuse_port=reuse_port, + ) servers.append(loop.run_until_complete(task)) if config.LISTEN_UNIX_SOCK and has_unix: remove_unix_socket(config.LISTEN_UNIX_SOCK) - task = asyncio.start_unix_server(handle_client_wrapper, config.LISTEN_UNIX_SOCK, - limit=get_to_tg_bufsize()) + task = asyncio.start_unix_server( + handle_client_wrapper, config.LISTEN_UNIX_SOCK, limit=get_to_tg_bufsize() + ) servers.append(loop.run_until_complete(task)) os.chmod(config.LISTEN_UNIX_SOCK, 0o666) if config.METRICS_PORT is not None: if config.METRICS_LISTEN_ADDR_IPV4: - task = asyncio.start_server(handle_metrics, config.METRICS_LISTEN_ADDR_IPV4, - config.METRICS_PORT) + task = asyncio.start_server( + handle_metrics, config.METRICS_LISTEN_ADDR_IPV4, config.METRICS_PORT + ) servers.append(loop.run_until_complete(task)) if config.METRICS_LISTEN_ADDR_IPV6 and socket.has_ipv6: - task = asyncio.start_server(handle_metrics, config.METRICS_LISTEN_ADDR_IPV6, - config.METRICS_PORT) + task = asyncio.start_server( + handle_metrics, config.METRICS_LISTEN_ADDR_IPV6, config.METRICS_PORT + ) servers.append(loop.run_until_complete(task)) return servers From 550582a65e06092fce34020ee1d261440c6dfc7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:14:35 +0000 Subject: [PATCH 4/4] Fix boolean environment variable conversion and add explanation for disabled connection pool Co-authored-by: xrh0905 <29017419+xrh0905@users.noreply.github.com> --- config.py | 23 +++++++++++++++++------ mtprotoproxy.py | 4 ++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/config.py b/config.py index 3d303822..ccc7f3bb 100644 --- a/config.py +++ b/config.py @@ -1,23 +1,34 @@ import os + + +def str_to_bool(value): + """Convert string to boolean.""" + if isinstance(value, bool): + return value + if isinstance(value, str): + return value.lower() in ("true", "1", "yes", "on") + return bool(value) + + PORT = 443 # name -> secret (32 hex chars) USERS = { - "tg": os.environ.get("TG_KEY", "00000000000000000000000000000001"), -# "tg2": "0123456789abcdef0123456789abcdef", + "tg": os.environ.get("TG_KEY", "00000000000000000000000000000001"), + # "tg2": "0123456789abcdef0123456789abcdef", } # Makes the proxy harder to detect # Can be incompatible with very old clients -SECURE_ONLY = os.environ.get('SECURE_ONLY', True) +SECURE_ONLY = str_to_bool(os.environ.get("SECURE_ONLY", "True")) # Makes the proxy even more hard to detect # Compatible only with the recent clients -TLS_ONLY = os.environ.get('TLS_ONLY', True) +TLS_ONLY = str_to_bool(os.environ.get("TLS_ONLY", "True")) # The domain for TLS, bad clients are proxied there # Use random existing domain, proxy checks it on start -TLS_DOMAIN = os.environ.get('TLS_DOMAIN', 'www.google.com') +TLS_DOMAIN = os.environ.get("TLS_DOMAIN", "www.google.com") # Tag for advertising, obtainable from @MTProxybot -AD_TAG = os.environ.get('AD_TAG', '3c09c680b76ee91a4c25ad51f742267d') +AD_TAG = os.environ.get("AD_TAG", "3c09c680b76ee91a4c25ad51f742267d") diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 2289c449..57fe0f07 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -556,6 +556,10 @@ def register_host_port(self, host, port, init_func): self.pools[(host, port, init_func)].append(connect_task) async def get_connection(self, host, port, init_func=None): + # Connection pool disabled to prevent Telegram showing "updating" status + # for users with fewer connections. This improves user experience by + # establishing fresh connections instead of reusing pooled ones. + # Original pooling logic commented out below: # self.register_host_port(host, port, init_func) # ret = None