mirror of
https://github.com/alexbers/mtprotoproxy.git
synced 2026-03-16 16:03:51 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0614c35020 | ||
|
|
368669a546 | ||
|
|
949ee12ed2 | ||
|
|
1f7ce9e977 | ||
|
|
8920faf650 | ||
|
|
34f743858c | ||
|
|
375fee1535 | ||
|
|
e0ea17978c | ||
|
|
8bb885ada5 | ||
|
|
bc841cff48 | ||
|
|
c89479000f | ||
|
|
74711c4212 | ||
|
|
51b2482dec | ||
|
|
87f4370927 | ||
|
|
a978eae922 | ||
|
|
88c8c57a44 |
@@ -1,7 +1,7 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y python3 python3-uvloop python3-cryptography python3-socks libcap2-bin ca-certificates && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install --no-install-recommends -y python3 python3-uvloop python3-cryptography python3-socks libcap2-bin ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
RUN setcap cap_net_bind_service=+ep /usr/bin/python3.10
|
RUN setcap cap_net_bind_service=+ep /usr/bin/python3.12
|
||||||
|
|
||||||
RUN useradd tgproxy -u 10000
|
RUN useradd tgproxy -u 10000
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ TG_DATACENTERS_V6 = [
|
|||||||
# This list will be updated in the runtime
|
# This list will be updated in the runtime
|
||||||
TG_MIDDLE_PROXIES_V4 = {
|
TG_MIDDLE_PROXIES_V4 = {
|
||||||
1: [("149.154.175.50", 8888)], -1: [("149.154.175.50", 8888)],
|
1: [("149.154.175.50", 8888)], -1: [("149.154.175.50", 8888)],
|
||||||
2: [("149.154.162.38", 80)], -2: [("149.154.162.38", 80)],
|
2: [("149.154.161.144", 8888)], -2: [("149.154.161.144", 8888)],
|
||||||
3: [("149.154.175.100", 8888)], -3: [("149.154.175.100", 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)],
|
4: [("91.108.4.136", 8888)], -4: [("149.154.165.109", 8888)],
|
||||||
5: [("91.108.56.181", 8888)], -5: [("91.108.56.181", 8888)]
|
5: [("91.108.56.183", 8888)], -5: [("91.108.56.183", 8888)]
|
||||||
}
|
}
|
||||||
|
|
||||||
TG_MIDDLE_PROXIES_V6 = {
|
TG_MIDDLE_PROXIES_V6 = {
|
||||||
@@ -47,7 +47,7 @@ TG_MIDDLE_PROXIES_V6 = {
|
|||||||
2: [("2001:67c:04e8:f002::d", 80)], -2: [("2001:67c:04e8:f002::d", 80)],
|
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)],
|
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)],
|
4: [("2001:67c:04e8:f004::d", 8888)], -4: [("2001:67c:04e8:f004::d", 8888)],
|
||||||
5: [("2001:b28:f23f:f005::d", 8888)], -5: [("2001:67c:04e8:f004::d", 8888)]
|
5: [("2001:b28:f23f:f005::d", 8888)], -5: [("2001:b28:f23f:f005::d", 8888)]
|
||||||
}
|
}
|
||||||
|
|
||||||
PROXY_SECRET = bytes.fromhex(
|
PROXY_SECRET = bytes.fromhex(
|
||||||
@@ -62,7 +62,6 @@ PREKEY_LEN = 32
|
|||||||
KEY_LEN = 32
|
KEY_LEN = 32
|
||||||
IV_LEN = 16
|
IV_LEN = 16
|
||||||
HANDSHAKE_LEN = 64
|
HANDSHAKE_LEN = 64
|
||||||
TLS_HANDSHAKE_LEN = 1 + 2 + 2 + 512
|
|
||||||
PROTO_TAG_POS = 56
|
PROTO_TAG_POS = 56
|
||||||
DC_IDX_POS = 60
|
DC_IDX_POS = 60
|
||||||
|
|
||||||
@@ -140,10 +139,10 @@ def init_config():
|
|||||||
# use middle proxy, necessary to show ad
|
# use middle proxy, necessary to show ad
|
||||||
conf_dict.setdefault("USE_MIDDLE_PROXY", len(conf_dict["AD_TAG"]) == 16)
|
conf_dict.setdefault("USE_MIDDLE_PROXY", len(conf_dict["AD_TAG"]) == 16)
|
||||||
|
|
||||||
# if IPv6 avaliable, use it by default
|
# if IPv6 available, use it by default, IPv6 with middle proxies is unstable now
|
||||||
conf_dict.setdefault("PREFER_IPV6", socket.has_ipv6)
|
conf_dict.setdefault("PREFER_IPV6", socket.has_ipv6 and not conf_dict["USE_MIDDLE_PROXY"])
|
||||||
|
|
||||||
# disables tg->client trafic reencryption, faster but less secure
|
# disables tg->client traffic reencryption, faster but less secure
|
||||||
conf_dict.setdefault("FAST_MODE", True)
|
conf_dict.setdefault("FAST_MODE", True)
|
||||||
|
|
||||||
# enables some working modes
|
# enables some working modes
|
||||||
@@ -265,6 +264,9 @@ def init_config():
|
|||||||
# telegram servers connect timeout in seconds
|
# telegram servers connect timeout in seconds
|
||||||
conf_dict.setdefault("TG_CONNECT_TIMEOUT", 10)
|
conf_dict.setdefault("TG_CONNECT_TIMEOUT", 10)
|
||||||
|
|
||||||
|
# drop connection if no data from telegram server for this many seconds
|
||||||
|
conf_dict.setdefault("TG_READ_TIMEOUT", 60)
|
||||||
|
|
||||||
# listen address for IPv4
|
# listen address for IPv4
|
||||||
conf_dict.setdefault("LISTEN_ADDR_IPV4", "0.0.0.0")
|
conf_dict.setdefault("LISTEN_ADDR_IPV4", "0.0.0.0")
|
||||||
|
|
||||||
@@ -484,7 +486,7 @@ myrandom = MyRandom()
|
|||||||
|
|
||||||
|
|
||||||
class TgConnectionPool:
|
class TgConnectionPool:
|
||||||
MAX_CONNS_IN_POOL = 64
|
MAX_CONNS_IN_POOL = 16
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.pools = {}
|
self.pools = {}
|
||||||
@@ -501,6 +503,16 @@ class TgConnectionPool:
|
|||||||
timeout=config.TG_CONNECT_TIMEOUT)
|
timeout=config.TG_CONNECT_TIMEOUT)
|
||||||
return reader_tgt, writer_tgt
|
return reader_tgt, writer_tgt
|
||||||
|
|
||||||
|
def is_conn_dead(self, reader, writer):
|
||||||
|
if writer.transport.is_closing():
|
||||||
|
return True
|
||||||
|
raw_reader = reader
|
||||||
|
while hasattr(raw_reader, 'upstream'):
|
||||||
|
raw_reader = raw_reader.upstream
|
||||||
|
if raw_reader.at_eof():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def register_host_port(self, host, port, init_func):
|
def register_host_port(self, host, port, init_func):
|
||||||
if (host, port, init_func) not in self.pools:
|
if (host, port, init_func) not in self.pools:
|
||||||
self.pools[(host, port, init_func)] = []
|
self.pools[(host, port, init_func)] = []
|
||||||
@@ -513,15 +525,16 @@ class TgConnectionPool:
|
|||||||
self.register_host_port(host, port, init_func)
|
self.register_host_port(host, port, init_func)
|
||||||
|
|
||||||
ret = None
|
ret = None
|
||||||
for task in self.pools[(host, port, init_func)][::]:
|
for task in self.pools[(host, port, init_func)][:]:
|
||||||
if task.done():
|
if task.done():
|
||||||
if task.exception():
|
if task.exception():
|
||||||
self.pools[(host, port, init_func)].remove(task)
|
self.pools[(host, port, init_func)].remove(task)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
reader, writer, *other = task.result()
|
reader, writer, *other = task.result()
|
||||||
if writer.transport.is_closing():
|
if self.is_conn_dead(reader, writer):
|
||||||
self.pools[(host, port, init_func)].remove(task)
|
self.pools[(host, port, init_func)].remove(task)
|
||||||
|
writer.transport.abort()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not ret:
|
if not ret:
|
||||||
@@ -658,7 +671,7 @@ class CryptoWrappedStreamReader(LayeredStreamReaderBase):
|
|||||||
|
|
||||||
needed_till_full_block = -len(data) % self.block_size
|
needed_till_full_block = -len(data) % self.block_size
|
||||||
if needed_till_full_block > 0:
|
if needed_till_full_block > 0:
|
||||||
data += self.upstream.readexactly(needed_till_full_block)
|
data += await self.upstream.readexactly(needed_till_full_block)
|
||||||
return self.decryptor.decrypt(data)
|
return self.decryptor.decrypt(data)
|
||||||
|
|
||||||
async def readexactly(self, n):
|
async def readexactly(self, n):
|
||||||
@@ -779,7 +792,7 @@ class MTProtoCompactFrameStreamWriter(LayeredStreamWriterBase):
|
|||||||
|
|
||||||
def write(self, data, extra={}):
|
def write(self, data, extra={}):
|
||||||
SMALL_PKT_BORDER = 0x7f
|
SMALL_PKT_BORDER = 0x7f
|
||||||
LARGE_PKT_BORGER = 256 ** 3
|
LARGE_PKT_BORDER = 256 ** 3
|
||||||
|
|
||||||
if len(data) % 4 != 0:
|
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))
|
||||||
@@ -792,7 +805,7 @@ class MTProtoCompactFrameStreamWriter(LayeredStreamWriterBase):
|
|||||||
|
|
||||||
if len_div_four < SMALL_PKT_BORDER:
|
if len_div_four < SMALL_PKT_BORDER:
|
||||||
return self.upstream.write(bytes([len_div_four]) + data)
|
return self.upstream.write(bytes([len_div_four]) + data)
|
||||||
elif len_div_four < LARGE_PKT_BORGER:
|
elif len_div_four < LARGE_PKT_BORDER:
|
||||||
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:
|
else:
|
||||||
print_err("Attempted to send too large pkt len =", len(data))
|
print_err("Attempted to send too large pkt len =", len(data))
|
||||||
@@ -868,6 +881,7 @@ class ProxyReqStreamReader(LayeredStreamReaderBase):
|
|||||||
RPC_PROXY_ANS = b"\x0d\xda\x03\x44"
|
RPC_PROXY_ANS = b"\x0d\xda\x03\x44"
|
||||||
RPC_CLOSE_EXT = b"\xa2\x34\xb6\x5e"
|
RPC_CLOSE_EXT = b"\xa2\x34\xb6\x5e"
|
||||||
RPC_SIMPLE_ACK = b"\x9b\x40\xac\x3b"
|
RPC_SIMPLE_ACK = b"\x9b\x40\xac\x3b"
|
||||||
|
RPC_UNKNOWN = b'\xdf\xa2\x30\x57'
|
||||||
|
|
||||||
data = await self.upstream.read(1)
|
data = await self.upstream.read(1)
|
||||||
|
|
||||||
@@ -886,8 +900,11 @@ class ProxyReqStreamReader(LayeredStreamReaderBase):
|
|||||||
conn_id, confirm = data[4:12], data[12:16]
|
conn_id, confirm = data[4:12], data[12:16]
|
||||||
return confirm, {"SIMPLE_ACK": True}
|
return confirm, {"SIMPLE_ACK": True}
|
||||||
|
|
||||||
|
if ans_type == RPC_UNKNOWN:
|
||||||
|
return b"", {"SKIP_SEND": True}
|
||||||
|
|
||||||
print_err("unknown rpc ans type:", ans_type)
|
print_err("unknown rpc ans type:", ans_type)
|
||||||
return b""
|
return b"", {"SKIP_SEND": True}
|
||||||
|
|
||||||
|
|
||||||
class ProxyReqStreamWriter(LayeredStreamWriterBase):
|
class ProxyReqStreamWriter(LayeredStreamWriterBase):
|
||||||
@@ -1232,7 +1249,7 @@ async def handle_handshake(reader, writer):
|
|||||||
global last_client_ips
|
global last_client_ips
|
||||||
global last_clients_with_same_handshake
|
global last_clients_with_same_handshake
|
||||||
|
|
||||||
TLS_START_BYTES = b"\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03"
|
TLS_START_BYTES = b"\x16\x03\x01"
|
||||||
|
|
||||||
if writer.transport.is_closing() or writer.get_extra_info("peername") is None:
|
if writer.transport.is_closing() or writer.get_extra_info("peername") is None:
|
||||||
return False
|
return False
|
||||||
@@ -1258,7 +1275,13 @@ async def handle_handshake(reader, writer):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if is_tls_handshake:
|
if is_tls_handshake:
|
||||||
handshake += await reader.readexactly(TLS_HANDSHAKE_LEN - len(handshake))
|
handshake += await reader.readexactly(2)
|
||||||
|
tls_handshake_len = int.from_bytes(handshake[-2:], "big")
|
||||||
|
if tls_handshake_len < 512:
|
||||||
|
is_tls_handshake = False
|
||||||
|
|
||||||
|
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:
|
if not tls_handshake_result:
|
||||||
@@ -1355,7 +1378,7 @@ async def do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=None):
|
|||||||
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
|
return False
|
||||||
except ConnectionAbortedError as E:
|
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, dc, TG_DATACENTER_PORT, E))
|
||||||
return False
|
return False
|
||||||
except (OSError, asyncio.TimeoutError) as E:
|
except (OSError, asyncio.TimeoutError) as E:
|
||||||
print_err("Unable to connect to", dc, TG_DATACENTER_PORT)
|
print_err("Unable to connect to", dc, TG_DATACENTER_PORT)
|
||||||
@@ -1564,12 +1587,19 @@ async def do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port):
|
|||||||
async def tg_connect_reader_to_writer(rd, wr, user, rd_buf_size, is_upstream):
|
async def tg_connect_reader_to_writer(rd, wr, user, rd_buf_size, is_upstream):
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
data = await rd.read(rd_buf_size)
|
if not is_upstream:
|
||||||
|
data = await asyncio.wait_for(rd.read(rd_buf_size),
|
||||||
|
timeout=config.TG_READ_TIMEOUT)
|
||||||
|
else:
|
||||||
|
data = await rd.read(rd_buf_size)
|
||||||
if isinstance(data, tuple):
|
if isinstance(data, tuple):
|
||||||
data, extra = data
|
data, extra = data
|
||||||
else:
|
else:
|
||||||
extra = {}
|
extra = {}
|
||||||
|
|
||||||
|
if extra.get("SKIP_SEND"):
|
||||||
|
continue
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
wr.write_eof()
|
wr.write_eof()
|
||||||
await wr.drain()
|
await wr.drain()
|
||||||
@@ -1582,7 +1612,7 @@ async def tg_connect_reader_to_writer(rd, wr, user, rd_buf_size, is_upstream):
|
|||||||
|
|
||||||
wr.write(data, extra)
|
wr.write(data, extra)
|
||||||
await wr.drain()
|
await wr.drain()
|
||||||
except (OSError, asyncio.IncompleteReadError) as e:
|
except (OSError, asyncio.IncompleteReadError, asyncio.TimeoutError) as e:
|
||||||
# print_err(e)
|
# print_err(e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1918,6 +1948,16 @@ async def get_encrypted_cert(host, port, server_name):
|
|||||||
if record3_type != 23:
|
if record3_type != 23:
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
|
if len(record3) < MIN_CERT_LEN:
|
||||||
|
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
|
||||||
|
print_err(msg)
|
||||||
|
|
||||||
|
return record4
|
||||||
|
|
||||||
return record3
|
return record3
|
||||||
|
|
||||||
|
|
||||||
@@ -1978,7 +2018,8 @@ async def get_srv_time():
|
|||||||
continue
|
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")
|
srv_time = datetime.datetime.strptime(line, "%a, %d %b %Y %H:%M:%S %Z")
|
||||||
now_time = datetime.datetime.utcnow()
|
srv_time = srv_time.replace(tzinfo=datetime.timezone.utc)
|
||||||
|
now_time = datetime.datetime.now(datetime.timezone.utc)
|
||||||
is_time_skewed = (now_time-srv_time).total_seconds() > MAX_TIME_SKEW
|
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:
|
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("Time skew detected, please set the clock")
|
||||||
@@ -2124,15 +2165,15 @@ def print_tg_info():
|
|||||||
for ip in ip_addrs:
|
for ip in ip_addrs:
|
||||||
if config.MODES["classic"]:
|
if config.MODES["classic"]:
|
||||||
params = {"server": ip, "port": config.PORT, "secret": secret}
|
params = {"server": ip, "port": config.PORT, "secret": secret}
|
||||||
params_encodeded = urllib.parse.urlencode(params, safe=':')
|
params_encoded = urllib.parse.urlencode(params, safe=':')
|
||||||
classic_link = "tg://proxy?{}".format(params_encodeded)
|
classic_link = "tg://proxy?{}".format(params_encoded)
|
||||||
proxy_links.append({"user": user, "link": classic_link})
|
proxy_links.append({"user": user, "link": classic_link})
|
||||||
print("{}: {}".format(user, classic_link), flush=True)
|
print("{}: {}".format(user, classic_link), flush=True)
|
||||||
|
|
||||||
if config.MODES["secure"]:
|
if config.MODES["secure"]:
|
||||||
params = {"server": ip, "port": config.PORT, "secret": "dd" + secret}
|
params = {"server": ip, "port": config.PORT, "secret": "dd" + secret}
|
||||||
params_encodeded = urllib.parse.urlencode(params, safe=':')
|
params_encoded = urllib.parse.urlencode(params, safe=':')
|
||||||
dd_link = "tg://proxy?{}".format(params_encodeded)
|
dd_link = "tg://proxy?{}".format(params_encoded)
|
||||||
proxy_links.append({"user": user, "link": dd_link})
|
proxy_links.append({"user": user, "link": dd_link})
|
||||||
print("{}: {}".format(user, dd_link), flush=True)
|
print("{}: {}".format(user, dd_link), flush=True)
|
||||||
|
|
||||||
@@ -2142,8 +2183,8 @@ def print_tg_info():
|
|||||||
# tls_secret = bytes.fromhex("ee" + secret) + config.TLS_DOMAIN.encode()
|
# tls_secret = bytes.fromhex("ee" + secret) + config.TLS_DOMAIN.encode()
|
||||||
# tls_secret_base64 = base64.urlsafe_b64encode(tls_secret)
|
# tls_secret_base64 = base64.urlsafe_b64encode(tls_secret)
|
||||||
params = {"server": ip, "port": config.PORT, "secret": tls_secret}
|
params = {"server": ip, "port": config.PORT, "secret": tls_secret}
|
||||||
params_encodeded = urllib.parse.urlencode(params, safe=':')
|
params_encoded = urllib.parse.urlencode(params, safe=':')
|
||||||
tls_link = "tg://proxy?{}".format(params_encodeded)
|
tls_link = "tg://proxy?{}".format(params_encoded)
|
||||||
proxy_links.append({"user": user, "link": tls_link})
|
proxy_links.append({"user": user, "link": tls_link})
|
||||||
print("{}: {}".format(user, tls_link), flush=True)
|
print("{}: {}".format(user, tls_link), flush=True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user