5 Commits

Author SHA1 Message Date
Alexander Bersenev
66ac871a74 remove the hackish logic against replay attacks because it stopped to work with new android clients
see https://github.com/alexbers/mtprotoproxy/issues/183
2020-01-16 19:05:18 +05:00
Alexander Bersenev
c5344df0eb Add wiki links to readme 2019-12-14 14:55:10 +05:00
Alexander Bersenev
93ad268d48 add ip logging 2019-11-29 17:30:10 +05:00
Alexander Bersenev
1c29465b6e change comment in config 2019-11-22 02:11:41 +05:00
Alexander Bersenev
d41b4abf35 MODES option instead of SECURE_ONLY and TLS_ONLY 2019-11-22 02:05:05 +05:00
3 changed files with 115 additions and 62 deletions

View File

@@ -20,6 +20,11 @@ To advertise a channel get a tag from **@MTProxybot** and put it to *config.py*.
The proxy performance should be enough to comfortably serve about 4 000 simultaneous users on
the VDS instance with 1 CPU core and 1024MB RAM.
## More Instructions ##
- [Running without Docker](https://github.com/alexbers/mtprotoproxy/wiki/Running-Without-Docker)
- [Optimization and fine tuning](https://github.com/alexbers/mtprotoproxy/wiki/Optimization-and-Fine-Tuning)
## Advanced Usage ##
The proxy can be launched:

View File

@@ -3,18 +3,23 @@ PORT = 443
# name -> secret (32 hex chars)
USERS = {
"tg": "00000000000000000000000000000001",
# "tg2": "0123456789abcdef0123456789abcdef",
# "tg2": "0123456789abcdef0123456789abcdef",
}
# Makes the proxy harder to detect
# Can be incompatible with very old clients
SECURE_ONLY = True
MODES = {
# Classic mode, easy to detect
"classic": False,
# Makes the proxy even more hard to detect
# Compatible only with the recent clients
TLS_ONLY = True
# Makes the proxy harder to detect
# Can be incompatible with very old clients
"secure": False,
# The domain for TLS, bad clients are proxied there
# 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
# Use random existing domain, proxy checks it on start
# TLS_DOMAIN = "www.google.com"

View File

@@ -82,12 +82,13 @@ STAT_DURATION_BUCKETS = [0.1, 0.5, 1, 2, 5, 15, 60, 300, 600, 1800, 2**31 - 1]
my_ip_info = {"ipv4": None, "ipv6": None}
used_handshakes = collections.OrderedDict()
client_ips = collections.OrderedDict()
last_client_ips = {}
disable_middle_proxy = False
is_time_skewed = False
fake_cert_len = random.randrange(1024, 4096)
mask_host_cached_ip = None
last_clients_with_time_skew = {}
last_clients_with_first_pkt_error = collections.Counter()
last_clients_with_same_handshake = collections.Counter()
proxy_start_time = 0
proxy_links = []
@@ -109,12 +110,12 @@ def init_config():
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["MODES"] = {"classic": False, "secure": True, "tls": True}
if len(sys.argv) > 3:
conf_dict["AD_TAG"] = sys.argv[3]
if len(sys.argv) > 4:
conf_dict["TLS_DOMAIN"] = sys.argv[4]
conf_dict["TLS_ONLY"] = True
conf_dict["SECURE_ONLY"] = True
conf_dict["MODES"] = {"classic": False, "secure": False, "tls": True}
conf_dict = {k: v for k, v in conf_dict.items() if k.isupper()}
@@ -133,11 +134,43 @@ def init_config():
# disables tg->client trafic reencryption, faster but less secure
conf_dict.setdefault("FAST_MODE", True)
# doesn't allow to connect in not-secure mode
conf_dict.setdefault("SECURE_ONLY", False)
# enables some working modes
modes = conf_dict.get("MODES", {})
# allows to connect in tls mode only
conf_dict.setdefault("TLS_ONLY", False)
if "MODES" not in conf_dict:
modes.setdefault("classic", True)
modes.setdefault("secure", True)
modes.setdefault("tls", True)
else:
modes.setdefault("classic", False)
modes.setdefault("secure", False)
modes.setdefault("tls", False)
legacy_warning = False
if "SECURE_ONLY" in conf_dict:
legacy_warning = True
modes["classic"] = not bool(conf_dict["SECURE_ONLY"])
if "TLS_ONLY" in conf_dict:
legacy_warning = True
if conf_dict["TLS_ONLY"]:
modes["classic"] = False
modes["secure"] = False
if not modes["classic"] and not modes["secure"] and not modes["tls"]:
print_err("No known modes enabled, enabling tls-only mode")
modes["tls"] = True
if legacy_warning:
print_err("Legacy options SECURE_ONLY or TLS_ONLY detected")
print_err("Please use MODES in your config instead:")
print_err("MODES = {")
print_err(' "classic": %s,' % modes["classic"])
print_err(' "secure": %s,' % modes["secure"])
print_err(' "tls": %s' % modes["tls"])
print_err("}")
conf_dict["MODES"] = modes
# accept incoming connections only with proxy protocol v1/v2, useful for nginx and haproxy
conf_dict.setdefault("PROXY_PROTOCOL", False)
@@ -180,8 +213,8 @@ def init_config():
# length of used handshake randoms for active fingerprinting protection, zero to disable
conf_dict.setdefault("REPLAY_CHECK_LEN", 65536)
# block bad first packets to even more protect against replay-based fingerprinting
conf_dict.setdefault("BLOCK_IF_FIRST_PKT_BAD", not conf_dict["TLS_ONLY"])
# length of last client ip addresses for logging
conf_dict.setdefault("CLIENT_IPS_LEN", 131072)
# delay in seconds between stats printing
conf_dict.setdefault("STATS_PRINT_PERIOD", 600)
@@ -1031,6 +1064,8 @@ async def handle_bad_client(reader_clt, writer_clt, handshake):
async def handle_fake_tls_handshake(handshake, reader, writer, peer):
global used_handshakes
global client_ips
global last_client_ips
global last_clients_with_time_skew
global last_clients_with_same_handshake
global fake_cert_len
@@ -1103,6 +1138,13 @@ async def handle_fake_tls_handshake(handshake, reader, writer, peer):
used_handshakes.popitem(last=False)
used_handshakes[digest[:DIGEST_HALFLEN]] = True
if config.CLIENT_IPS_LEN > 0:
while len(client_ips) >= config.CLIENT_IPS_LEN:
client_ips.popitem(last=False)
if peer[0] not in client_ips:
client_ips[peer[0]] = True
last_client_ips[peer[0]] = True
reader = FakeTLSStreamReader(reader)
writer = FakeTLSStreamWriter(writer)
return reader, writer
@@ -1167,6 +1209,8 @@ async def handle_proxy_protocol(reader, peer=None):
async def handle_handshake(reader, writer):
global used_handshakes
global client_ips
global last_client_ips
global last_clients_with_same_handshake
TLS_START_BYTES = b"\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03"
@@ -1202,7 +1246,7 @@ async def handle_handshake(reader, writer):
reader, writer = tls_handshake_result
handshake = await reader.readexactly(HANDSHAKE_LEN)
else:
if config.TLS_ONLY:
if not config.MODES["classic"] and not config.MODES["secure"]:
await handle_bad_client(reader, writer, handshake)
return False
handshake += await reader.readexactly(HANDSHAKE_LEN - len(handshake))
@@ -1232,8 +1276,14 @@ async def handle_handshake(reader, writer):
if proto_tag not in (PROTO_TAG_ABRIDGED, PROTO_TAG_INTERMEDIATE, PROTO_TAG_SECURE):
continue
if config.SECURE_ONLY and proto_tag != PROTO_TAG_SECURE:
continue
if proto_tag == PROTO_TAG_SECURE:
if is_tls_handshake and not config.MODES["tls"]:
continue
if not is_tls_handshake and not config.MODES["secure"]:
continue
else:
if not config.MODES["classic"]:
continue
dc_idx = int.from_bytes(decrypted[DC_IDX_POS:DC_IDX_POS+2], "little", signed=True)
@@ -1242,6 +1292,13 @@ async def handle_handshake(reader, writer):
used_handshakes.popitem(last=False)
used_handshakes[dec_prekey_and_iv] = True
if config.CLIENT_IPS_LEN > 0:
while len(client_ips) >= config.CLIENT_IPS_LEN:
client_ips.popitem(last=False)
if peer[0] not in client_ips:
client_ips[peer[0]] = True
last_client_ips[peer[0]] = True
reader = CryptoWrappedStreamReader(reader, decryptor)
writer = CryptoWrappedStreamWriter(writer, encryptor)
return reader, writer, proto_tag, user, dc_idx, enc_key + enc_iv, peer
@@ -1545,9 +1602,7 @@ async def handle_client(reader_clt, writer_clt):
else:
return
async def connect_reader_to_writer(rd, wr, user, rd_buf_size, block_if_first_pkt_bad=False):
global last_clients_with_first_pkt_error
is_first_pkt = True
async def connect_reader_to_writer(rd, wr, user, rd_buf_size):
try:
while True:
data = await rd.read(rd_buf_size)
@@ -1556,18 +1611,6 @@ async def handle_client(reader_clt, writer_clt):
else:
extra = {}
# protection against replay-based fingerprinting
if is_first_pkt:
is_first_pkt = False
ERR_PKT_DATA = b'l\xfe\xff\xff'
if block_if_first_pkt_bad and data == ERR_PKT_DATA:
last_clients_with_first_pkt_error[cl_ip] += 1
wr.write_eof()
await wr.drain()
return
if not data:
wr.write_eof()
await wr.drain()
@@ -1580,8 +1623,7 @@ async def handle_client(reader_clt, writer_clt):
# print_err(e)
pass
tg_to_clt = connect_reader_to_writer(reader_tg, writer_clt, user, get_to_clt_bufsize(),
block_if_first_pkt_bad=config.BLOCK_IF_FIRST_PKT_BAD)
tg_to_clt = connect_reader_to_writer(reader_tg, writer_clt, user, get_to_clt_bufsize())
clt_to_tg = connect_reader_to_writer(reader_clt, writer_tg, user, get_to_tg_bufsize())
task_tg_to_clt = asyncio.ensure_future(tg_to_clt)
task_clt_to_tg = asyncio.ensure_future(clt_to_tg)
@@ -1671,7 +1713,6 @@ async def handle_metrics(reader, writer):
global proxy_start_time
global proxy_links
global last_clients_with_time_skew
global last_clients_with_first_pkt_error
global last_clients_with_same_handshake
client_ip = writer.get_extra_info("peername")[0]
@@ -1729,8 +1770,8 @@ async def handle_metrics(reader, writer):
async def stats_printer():
global user_stats
global last_client_ips
global last_clients_with_time_skew
global last_clients_with_first_pkt_error
global last_clients_with_same_handshake
while True:
@@ -1743,18 +1784,19 @@ async def stats_printer():
stat["octets"] / 1000000, stat["msgs"]))
print(flush=True)
if last_client_ips:
print("New IPs:")
for ip in last_client_ips:
print(ip)
print(flush=True)
last_client_ips.clear()
if last_clients_with_time_skew:
print("Clients with time skew (possible replay-attackers):")
for ip, skew_minutes in last_clients_with_time_skew.items():
print("%s, clocks were %d minutes behind" % (ip, skew_minutes))
print(flush=True)
last_clients_with_time_skew.clear()
if last_clients_with_first_pkt_error:
print("Clients with error on the first packet (possible replay-attackers):")
for ip, times in last_clients_with_first_pkt_error.items():
print("%s, %d times" % (ip, times))
print(flush=True)
last_clients_with_first_pkt_error.clear()
if last_clients_with_same_handshake:
print("Clients with duplicate handshake (likely replay-attackers):")
for ip, times in last_clients_with_same_handshake.items():
@@ -2018,7 +2060,7 @@ def print_tg_info():
if config.PORT == 3256:
print("The default port 3256 is used, this is not recommended", flush=True)
if config.TLS_ONLY:
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_default_warning = True
@@ -2030,29 +2072,30 @@ def print_tg_info():
for user, secret in sorted(config.USERS.items(), key=lambda x: x[0]):
for ip in ip_addrs:
if not config.TLS_ONLY:
if not config.SECURE_ONLY:
params = {"server": ip, "port": config.PORT, "secret": secret}
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["classic"]:
params = {"server": ip, "port": config.PORT, "secret": secret}
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=':')
dd_link = "tg://proxy?{}".format(params_encodeded)
proxy_links.append({"user": user, "link": dd_link})
print("{}: {}".format(user, dd_link), flush=True)
tls_secret = "ee" + secret + config.TLS_DOMAIN.encode().hex()
# the base64 links is buggy on ios
# 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=':')
tls_link = "tg://proxy?{}".format(params_encodeded)
proxy_links.append({"user": user, "link": tls_link})
print("{}: {}".format(user, tls_link), flush=True)
if config.MODES["tls"]:
tls_secret = "ee" + secret + config.TLS_DOMAIN.encode().hex()
# the base64 links is buggy on ios
# 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=':')
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"]: