Nailioq commited on
Commit
b460680
·
verified ·
1 Parent(s): dc31742

Upload 7 files

Browse files
Files changed (7) hide show
  1. README.md +39 -10
  2. config.py +26 -0
  3. mtprotoproxy.py +2414 -0
  4. requirements.txt +4 -0
  5. runner.py +37 -0
  6. test_docker.sh +1 -0
  7. webui.py +137 -0
README.md CHANGED
@@ -1,10 +1,39 @@
1
- ---
2
- title: MT
3
- emoji: 🐢
4
- colorFrom: purple
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MTProto Proxy with WebUI
2
+
3
+ This is a fast, lightweight MTProto Proxy (based on the excellent async Python implementation by `alexbers`), wrapped in a single Docker container along with a real-time Flask WebUI dashboard to monitor users and system metrics.
4
+
5
+ ## Features
6
+ - **High Performance**: Uses Python 3.12 with `uvloop` for maximum async performance.
7
+ - **WebUI Dashboard**: Live monitoring of CPU load, Memory usage, and Active MTProto Connections on port `7860`.
8
+ - **All-in-One**: Single Docker container for both the proxy and the web interface.
9
+ - **Secure**: Supports MTProto TLS mode for bypassing DPI.
10
+
11
+ ## Getting Started
12
+
13
+ ### 1. Configure the Proxy
14
+ Open `config.py` and modify it as needed. By default, it runs on port `443` and uses TLS mode.
15
+ Add or modify users in the `USERS` dictionary. Generate a secure secret (32 hex characters) for each user:
16
+ ```python
17
+ USERS = {
18
+ "user1": "b08b584d41348b61a357549641743a29", # Replace with your random secret
19
+ }
20
+ ```
21
+
22
+ ### 2. Build the Docker Image
23
+ ```bash
24
+ docker build -t mtproto-proxy-webui .
25
+ ```
26
+
27
+ ### 3. Run the Docker Container
28
+ Run the container, making sure to map the MTProto proxy port (default `443`) and the WebUI port (`7860`):
29
+ ```bash
30
+ docker run -d --name mtproto-webui -p 443:443 -p 7860:7860 mtproto-proxy-webui
31
+ ```
32
+
33
+ *(Note: If you change the `PORT` in `config.py`, make sure to update the `-p` mapping accordingly, e.g., `-p 8443:8443`)*
34
+
35
+ ## Accessing the WebUI
36
+ Once the container is running, open your browser and navigate to:
37
+ `http://<your-server-ip>:7860`
38
+
39
+ You will see a real-time dashboard displaying system metrics and active connection statistics for each user.
config.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PORT = 443
2
+
3
+ # name -> secret (32 hex chars)
4
+ USERS = {
5
+ "tg": "00000000000000000000000000000001",
6
+ }
7
+
8
+ MODES = {
9
+ # Classic mode, easy to detect
10
+ "classic": False,
11
+
12
+ # Makes the proxy harder to detect
13
+ # Can be incompatible with very old clients
14
+ "secure": False,
15
+
16
+ # Makes the proxy even more hard to detect
17
+ # Can be incompatible with old clients
18
+ "tls": True
19
+ }
20
+
21
+ # The domain for TLS mode, bad clients are proxied there
22
+ # Use random existing domain, proxy checks it on start
23
+ TLS_DOMAIN = "www.google.com"
24
+
25
+ # Tag for advertising, obtainable from @MTProxybot
26
+ AD_TAG = ""
mtprotoproxy.py ADDED
@@ -0,0 +1,2414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import asyncio
4
+ import socket
5
+ import urllib.parse
6
+ import urllib.request
7
+ import collections
8
+ import time
9
+ import datetime
10
+ import hmac
11
+ import base64
12
+ import hashlib
13
+ import random
14
+ import binascii
15
+ import sys
16
+ import re
17
+ import runpy
18
+ import signal
19
+ import os
20
+ import stat
21
+ import traceback
22
+
23
+
24
+ TG_DATACENTER_PORT = 443
25
+
26
+ TG_DATACENTERS_V4 = [
27
+ "149.154.175.50", "149.154.167.51", "149.154.175.100",
28
+ "149.154.167.91", "149.154.171.5"
29
+ ]
30
+
31
+ TG_DATACENTERS_V6 = [
32
+ "2001:b28:f23d:f001::a", "2001:67c:04e8:f002::a", "2001:b28:f23d:f003::a",
33
+ "2001:67c:04e8:f004::a", "2001:b28:f23f:f005::a"
34
+ ]
35
+
36
+ # This list will be updated in the runtime
37
+ TG_MIDDLE_PROXIES_V4 = {
38
+ 1: [("149.154.175.50", 8888)], -1: [("149.154.175.50", 8888)],
39
+ 2: [("149.154.161.144", 8888)], -2: [("149.154.161.144", 8888)],
40
+ 3: [("149.154.175.100", 8888)], -3: [("149.154.175.100", 8888)],
41
+ 4: [("91.108.4.136", 8888)], -4: [("149.154.165.109", 8888)],
42
+ 5: [("91.108.56.183", 8888)], -5: [("91.108.56.183", 8888)]
43
+ }
44
+
45
+ TG_MIDDLE_PROXIES_V6 = {
46
+ 1: [("2001:b28:f23d:f001::d", 8888)], -1: [("2001:b28:f23d:f001::d", 8888)],
47
+ 2: [("2001:67c:04e8:f002::d", 80)], -2: [("2001:67c:04e8:f002::d", 80)],
48
+ 3: [("2001:b28:f23d:f003::d", 8888)], -3: [("2001:b28:f23d:f003::d", 8888)],
49
+ 4: [("2001:67c:04e8:f004::d", 8888)], -4: [("2001:67c:04e8:f004::d", 8888)],
50
+ 5: [("2001:b28:f23f:f005::d", 8888)], -5: [("2001:b28:f23f:f005::d", 8888)]
51
+ }
52
+
53
+ PROXY_SECRET = bytes.fromhex(
54
+ "c4f9faca9678e6bb48ad6c7e2ce5c0d24430645d554addeb55419e034da62721" +
55
+ "d046eaab6e52ab14a95a443ecfb3463e79a05a66612adf9caeda8be9a80da698" +
56
+ "6fb0a6ff387af84d88ef3a6413713e5c3377f6e1a3d47d99f5e0c56eece8f05c" +
57
+ "54c490b079e31bef82ff0ee8f2b0a32756d249c5f21269816cb7061b265db212"
58
+ )
59
+
60
+ SKIP_LEN = 8
61
+ PREKEY_LEN = 32
62
+ KEY_LEN = 32
63
+ IV_LEN = 16
64
+ HANDSHAKE_LEN = 64
65
+ PROTO_TAG_POS = 56
66
+ DC_IDX_POS = 60
67
+
68
+ MIN_CERT_LEN = 1024
69
+
70
+ PROTO_TAG_ABRIDGED = b"\xef\xef\xef\xef"
71
+ PROTO_TAG_INTERMEDIATE = b"\xee\xee\xee\xee"
72
+ PROTO_TAG_SECURE = b"\xdd\xdd\xdd\xdd"
73
+
74
+ CBC_PADDING = 16
75
+ PADDING_FILLER = b"\x04\x00\x00\x00"
76
+
77
+ MIN_MSG_LEN = 12
78
+ MAX_MSG_LEN = 2 ** 24
79
+
80
+ STAT_DURATION_BUCKETS = [0.1, 0.5, 1, 2, 5, 15, 60, 300, 600, 1800, 2**31 - 1]
81
+
82
+ my_ip_info = {"ipv4": None, "ipv6": None}
83
+ used_handshakes = collections.OrderedDict()
84
+ client_ips = collections.OrderedDict()
85
+ last_client_ips = {}
86
+ disable_middle_proxy = False
87
+ is_time_skewed = False
88
+ fake_cert_len = random.randrange(1024, 4096)
89
+ mask_host_cached_ip = None
90
+ last_clients_with_time_skew = {}
91
+ last_clients_with_same_handshake = collections.Counter()
92
+ proxy_start_time = 0
93
+ proxy_links = []
94
+
95
+ stats = collections.Counter()
96
+ user_stats = collections.defaultdict(collections.Counter)
97
+
98
+ config = {}
99
+
100
+
101
+ def init_config():
102
+ global config
103
+ # we use conf_dict to protect the original config from exceptions when reloading
104
+ if len(sys.argv) < 2:
105
+ conf_dict = runpy.run_module("config")
106
+ elif len(sys.argv) == 2:
107
+ # launch with own config
108
+ conf_dict = runpy.run_path(sys.argv[1])
109
+ else:
110
+ # undocumented way of launching
111
+ conf_dict = {}
112
+ conf_dict["PORT"] = int(sys.argv[1])
113
+ secrets = sys.argv[2].split(",")
114
+ conf_dict["USERS"] = {"user%d" % i: secrets[i].zfill(32) for i in range(len(secrets))}
115
+ conf_dict["MODES"] = {"classic": False, "secure": True, "tls": True}
116
+ if len(sys.argv) > 3:
117
+ conf_dict["AD_TAG"] = sys.argv[3]
118
+ if len(sys.argv) > 4:
119
+ conf_dict["TLS_DOMAIN"] = sys.argv[4]
120
+ conf_dict["MODES"] = {"classic": False, "secure": False, "tls": True}
121
+
122
+ conf_dict = {k: v for k, v in conf_dict.items() if k.isupper()}
123
+
124
+ conf_dict.setdefault("PORT", 3256)
125
+ conf_dict.setdefault("USERS", {"tg": "00000000000000000000000000000000"})
126
+ conf_dict["AD_TAG"] = bytes.fromhex(conf_dict.get("AD_TAG", ""))
127
+
128
+ for user, secret in conf_dict["USERS"].items():
129
+ if not re.fullmatch("[0-9a-fA-F]{32}", secret):
130
+ fixed_secret = re.sub(r"[^0-9a-fA-F]", "", secret).zfill(32)[:32]
131
+
132
+ print_err("Bad secret for user %s, should be 32 hex chars, got %s. " % (user, secret))
133
+ print_err("Changing it to %s" % fixed_secret)
134
+
135
+ conf_dict["USERS"][user] = fixed_secret
136
+
137
+ # load advanced settings
138
+
139
+ # use middle proxy, necessary to show ad
140
+ conf_dict.setdefault("USE_MIDDLE_PROXY", len(conf_dict["AD_TAG"]) == 16)
141
+
142
+ # if IPv6 available, use it by default, IPv6 with middle proxies is unstable now
143
+ conf_dict.setdefault("PREFER_IPV6", socket.has_ipv6 and not conf_dict["USE_MIDDLE_PROXY"])
144
+
145
+ # disables tg->client traffic reencryption, faster but less secure
146
+ conf_dict.setdefault("FAST_MODE", True)
147
+
148
+ # enables some working modes
149
+ modes = conf_dict.get("MODES", {})
150
+
151
+ if "MODES" not in conf_dict:
152
+ modes.setdefault("classic", True)
153
+ modes.setdefault("secure", True)
154
+ modes.setdefault("tls", True)
155
+ else:
156
+ modes.setdefault("classic", False)
157
+ modes.setdefault("secure", False)
158
+ modes.setdefault("tls", False)
159
+
160
+ legacy_warning = False
161
+ if "SECURE_ONLY" in conf_dict:
162
+ legacy_warning = True
163
+ modes["classic"] = not bool(conf_dict["SECURE_ONLY"])
164
+
165
+ if "TLS_ONLY" in conf_dict:
166
+ legacy_warning = True
167
+ if conf_dict["TLS_ONLY"]:
168
+ modes["classic"] = False
169
+ modes["secure"] = False
170
+
171
+ if not modes["classic"] and not modes["secure"] and not modes["tls"]:
172
+ print_err("No known modes enabled, enabling tls-only mode")
173
+ modes["tls"] = True
174
+
175
+ if legacy_warning:
176
+ print_err("Legacy options SECURE_ONLY or TLS_ONLY detected")
177
+ print_err("Please use MODES in your config instead:")
178
+ print_err("MODES = {")
179
+ print_err(' "classic": %s,' % modes["classic"])
180
+ print_err(' "secure": %s,' % modes["secure"])
181
+ print_err(' "tls": %s' % modes["tls"])
182
+ print_err("}")
183
+
184
+ conf_dict["MODES"] = modes
185
+
186
+ # accept incoming connections only with proxy protocol v1/v2, useful for nginx and haproxy
187
+ conf_dict.setdefault("PROXY_PROTOCOL", False)
188
+
189
+ # set the tls domain for the proxy, has an influence only on starting message
190
+ conf_dict.setdefault("TLS_DOMAIN", "www.google.com")
191
+
192
+ # enable proxying bad clients to some host
193
+ conf_dict.setdefault("MASK", True)
194
+
195
+ # the next host to forward bad clients
196
+ conf_dict.setdefault("MASK_HOST", conf_dict["TLS_DOMAIN"])
197
+
198
+ # set the home domain for the proxy, has an influence only on the log message
199
+ conf_dict.setdefault("MY_DOMAIN", False)
200
+
201
+ # the next host's port to forward bad clients
202
+ conf_dict.setdefault("MASK_PORT", 443)
203
+
204
+ # use upstream SOCKS5 proxy
205
+ conf_dict.setdefault("SOCKS5_HOST", None)
206
+ conf_dict.setdefault("SOCKS5_PORT", None)
207
+ conf_dict.setdefault("SOCKS5_USER", None)
208
+ conf_dict.setdefault("SOCKS5_PASS", None)
209
+
210
+ if conf_dict["SOCKS5_HOST"] and conf_dict["SOCKS5_PORT"]:
211
+ # Disable the middle proxy if using socks, they are not compatible
212
+ conf_dict["USE_MIDDLE_PROXY"] = False
213
+
214
+ # user tcp connection limits, the mapping from name to the integer limit
215
+ # one client can create many tcp connections, up to 8
216
+ conf_dict.setdefault("USER_MAX_TCP_CONNS", {})
217
+
218
+ # expiration date for users in format of day/month/year
219
+ conf_dict.setdefault("USER_EXPIRATIONS", {})
220
+ for user in conf_dict["USER_EXPIRATIONS"]:
221
+ expiration = datetime.datetime.strptime(conf_dict["USER_EXPIRATIONS"][user], "%d/%m/%Y")
222
+ conf_dict["USER_EXPIRATIONS"][user] = expiration
223
+
224
+ # the data quota for user
225
+ conf_dict.setdefault("USER_DATA_QUOTA", {})
226
+
227
+ # length of used handshake randoms for active fingerprinting protection, zero to disable
228
+ conf_dict.setdefault("REPLAY_CHECK_LEN", 65536)
229
+
230
+ # accept clients with bad clocks. This reduces the protection against replay attacks
231
+ conf_dict.setdefault("IGNORE_TIME_SKEW", False)
232
+
233
+ # length of last client ip addresses for logging
234
+ conf_dict.setdefault("CLIENT_IPS_LEN", 131072)
235
+
236
+ # delay in seconds between stats printing
237
+ conf_dict.setdefault("STATS_PRINT_PERIOD", 600)
238
+
239
+ # delay in seconds between middle proxy info updates
240
+ conf_dict.setdefault("PROXY_INFO_UPDATE_PERIOD", 24*60*60)
241
+
242
+ # delay in seconds between time getting, zero means disabled
243
+ conf_dict.setdefault("GET_TIME_PERIOD", 10*60)
244
+
245
+ # delay in seconds between getting the length of certificate on the mask host
246
+ conf_dict.setdefault("GET_CERT_LEN_PERIOD", random.randrange(4*60*60, 6*60*60))
247
+
248
+ # max socket buffer size to the client direction, the more the faster, but more RAM hungry
249
+ # can be the tuple (low, users_margin, high) for the adaptive case. If no much users, use high
250
+ conf_dict.setdefault("TO_CLT_BUFSIZE", (16384, 100, 131072))
251
+
252
+ # max socket buffer size to the telegram servers direction, also can be the tuple
253
+ conf_dict.setdefault("TO_TG_BUFSIZE", 65536)
254
+
255
+ # keepalive period for clients in secs
256
+ conf_dict.setdefault("CLIENT_KEEPALIVE", 10*60)
257
+
258
+ # drop client after this timeout if the handshake fail
259
+ conf_dict.setdefault("CLIENT_HANDSHAKE_TIMEOUT", random.randrange(5, 15))
260
+
261
+ # if client doesn't confirm data for this number of seconds, it is dropped
262
+ conf_dict.setdefault("CLIENT_ACK_TIMEOUT", 5*60)
263
+
264
+ # telegram servers connect timeout in seconds
265
+ conf_dict.setdefault("TG_CONNECT_TIMEOUT", 10)
266
+
267
+ # drop connection if no data from telegram server for this many seconds
268
+ conf_dict.setdefault("TG_READ_TIMEOUT", 60)
269
+
270
+ # listen address for IPv4
271
+ conf_dict.setdefault("LISTEN_ADDR_IPV4", "0.0.0.0")
272
+
273
+ # listen address for IPv6
274
+ conf_dict.setdefault("LISTEN_ADDR_IPV6", "::")
275
+
276
+ # listen unix socket
277
+ conf_dict.setdefault("LISTEN_UNIX_SOCK", "")
278
+
279
+ # prometheus exporter listen port, use some random port here
280
+ conf_dict.setdefault("METRICS_PORT", None)
281
+
282
+ # prometheus listen addr ipv4
283
+ conf_dict.setdefault("METRICS_LISTEN_ADDR_IPV4", "0.0.0.0")
284
+
285
+ # prometheus listen addr ipv6
286
+ conf_dict.setdefault("METRICS_LISTEN_ADDR_IPV6", None)
287
+
288
+ # prometheus scrapers whitelist
289
+ conf_dict.setdefault("METRICS_WHITELIST", ["127.0.0.1", "::1"])
290
+
291
+ # export proxy link to prometheus
292
+ conf_dict.setdefault("METRICS_EXPORT_LINKS", False)
293
+
294
+ # default prefix for metrics
295
+ conf_dict.setdefault("METRICS_PREFIX", "mtprotoproxy_")
296
+
297
+ # allow access to config by attributes
298
+ config = type("config", (dict,), conf_dict)(conf_dict)
299
+
300
+
301
+ def apply_upstream_proxy_settings():
302
+ # apply socks settings in place
303
+ if config.SOCKS5_HOST and config.SOCKS5_PORT:
304
+ import socks
305
+ print_err("Socket-proxy mode activated, it is incompatible with advertising and uvloop")
306
+ socks.set_default_proxy(socks.PROXY_TYPE_SOCKS5, config.SOCKS5_HOST, config.SOCKS5_PORT,
307
+ username=config.SOCKS5_USER, password=config.SOCKS5_PASS)
308
+ if not hasattr(socket, "origsocket"):
309
+ socket.origsocket = socket.socket
310
+ socket.socket = socks.socksocket
311
+ elif hasattr(socket, "origsocket"):
312
+ socket.socket = socket.origsocket
313
+ del socket.origsocket
314
+
315
+
316
+ def try_use_cryptography_module():
317
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
318
+ from cryptography.hazmat.backends import default_backend
319
+
320
+ class CryptographyEncryptorAdapter:
321
+ __slots__ = ('encryptor', 'decryptor')
322
+
323
+ def __init__(self, cipher):
324
+ self.encryptor = cipher.encryptor()
325
+ self.decryptor = cipher.decryptor()
326
+
327
+ def encrypt(self, data):
328
+ return self.encryptor.update(data)
329
+
330
+ def decrypt(self, data):
331
+ return self.decryptor.update(data)
332
+
333
+ def create_aes_ctr(key, iv):
334
+ iv_bytes = int.to_bytes(iv, 16, "big")
335
+ cipher = Cipher(algorithms.AES(key), modes.CTR(iv_bytes), default_backend())
336
+ return CryptographyEncryptorAdapter(cipher)
337
+
338
+ def create_aes_cbc(key, iv):
339
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend())
340
+ return CryptographyEncryptorAdapter(cipher)
341
+
342
+ return create_aes_ctr, create_aes_cbc
343
+
344
+
345
+ def try_use_pycrypto_or_pycryptodome_module():
346
+ from Crypto.Cipher import AES
347
+ from Crypto.Util import Counter
348
+
349
+ def create_aes_ctr(key, iv):
350
+ ctr = Counter.new(128, initial_value=iv)
351
+ return AES.new(key, AES.MODE_CTR, counter=ctr)
352
+
353
+ def create_aes_cbc(key, iv):
354
+ return AES.new(key, AES.MODE_CBC, iv)
355
+
356
+ return create_aes_ctr, create_aes_cbc
357
+
358
+
359
+ def use_slow_bundled_cryptography_module():
360
+ import pyaes
361
+
362
+ msg = "To make the program a *lot* faster, please install cryptography module: "
363
+ msg += "pip install cryptography\n"
364
+ print(msg, flush=True, file=sys.stderr)
365
+
366
+ class BundledEncryptorAdapter:
367
+ __slots__ = ('mode', )
368
+
369
+ def __init__(self, mode):
370
+ self.mode = mode
371
+
372
+ def encrypt(self, data):
373
+ encrypter = pyaes.Encrypter(self.mode, pyaes.PADDING_NONE)
374
+ return encrypter.feed(data) + encrypter.feed()
375
+
376
+ def decrypt(self, data):
377
+ decrypter = pyaes.Decrypter(self.mode, pyaes.PADDING_NONE)
378
+ return decrypter.feed(data) + decrypter.feed()
379
+
380
+ def create_aes_ctr(key, iv):
381
+ ctr = pyaes.Counter(iv)
382
+ return pyaes.AESModeOfOperationCTR(key, ctr)
383
+
384
+ def create_aes_cbc(key, iv):
385
+ mode = pyaes.AESModeOfOperationCBC(key, iv)
386
+ return BundledEncryptorAdapter(mode)
387
+ return create_aes_ctr, create_aes_cbc
388
+
389
+
390
+ try:
391
+ create_aes_ctr, create_aes_cbc = try_use_cryptography_module()
392
+ except ImportError:
393
+ try:
394
+ create_aes_ctr, create_aes_cbc = try_use_pycrypto_or_pycryptodome_module()
395
+ except ImportError:
396
+ create_aes_ctr, create_aes_cbc = use_slow_bundled_cryptography_module()
397
+
398
+
399
+ def print_err(*params):
400
+ print(*params, file=sys.stderr, flush=True)
401
+
402
+
403
+ def ensure_users_in_user_stats():
404
+ global user_stats
405
+
406
+ for user in config.USERS:
407
+ user_stats[user].update()
408
+
409
+
410
+ def init_proxy_start_time():
411
+ global proxy_start_time
412
+ proxy_start_time = time.time()
413
+
414
+
415
+ def update_stats(**kw_stats):
416
+ global stats
417
+ stats.update(**kw_stats)
418
+
419
+
420
+ def update_user_stats(user, **kw_stats):
421
+ global user_stats
422
+ user_stats[user].update(**kw_stats)
423
+
424
+
425
+ def update_durations(duration):
426
+ global stats
427
+
428
+ for bucket in STAT_DURATION_BUCKETS:
429
+ if duration <= bucket:
430
+ break
431
+
432
+ update_stats(**{"connects_with_duration_le_%s" % str(bucket): 1})
433
+
434
+
435
+ def get_curr_connects_count():
436
+ global user_stats
437
+
438
+ all_connects = 0
439
+ for user, stat in user_stats.items():
440
+ all_connects += stat["curr_connects"]
441
+ return all_connects
442
+
443
+
444
+ def get_to_tg_bufsize():
445
+ if isinstance(config.TO_TG_BUFSIZE, int):
446
+ return config.TO_TG_BUFSIZE
447
+
448
+ low, margin, high = config.TO_TG_BUFSIZE
449
+ return high if get_curr_connects_count() < margin else low
450
+
451
+
452
+ def get_to_clt_bufsize():
453
+ if isinstance(config.TO_CLT_BUFSIZE, int):
454
+ return config.TO_CLT_BUFSIZE
455
+
456
+ low, margin, high = config.TO_CLT_BUFSIZE
457
+ return high if get_curr_connects_count() < margin else low
458
+
459
+
460
+ class MyRandom(random.Random):
461
+ def __init__(self):
462
+ super().__init__()
463
+ key = bytes([random.randrange(256) for i in range(32)])
464
+ iv = random.randrange(256**16)
465
+
466
+ self.encryptor = create_aes_ctr(key, iv)
467
+ self.buffer = bytearray()
468
+
469
+ def getrandbits(self, k):
470
+ numbytes = (k + 7) // 8
471
+ return int.from_bytes(self.getrandbytes(numbytes), 'big') >> (numbytes * 8 - k)
472
+
473
+ def getrandbytes(self, n):
474
+ CHUNK_SIZE = 512
475
+
476
+ while n > len(self.buffer):
477
+ data = int.to_bytes(super().getrandbits(CHUNK_SIZE*8), CHUNK_SIZE, "big")
478
+ self.buffer += self.encryptor.encrypt(data)
479
+
480
+ result = self.buffer[:n]
481
+ self.buffer = self.buffer[n:]
482
+ return bytes(result)
483
+
484
+
485
+ myrandom = MyRandom()
486
+
487
+
488
+ class TgConnectionPool:
489
+ MAX_CONNS_IN_POOL = 16
490
+
491
+ def __init__(self):
492
+ self.pools = {}
493
+
494
+ async def open_tg_connection(self, host, port, init_func=None):
495
+ task = asyncio.open_connection(host, port, limit=get_to_clt_bufsize())
496
+ reader_tgt, writer_tgt = await asyncio.wait_for(task, timeout=config.TG_CONNECT_TIMEOUT)
497
+
498
+ set_keepalive(writer_tgt.get_extra_info("socket"))
499
+ set_bufsizes(writer_tgt.get_extra_info("socket"), get_to_clt_bufsize(), get_to_tg_bufsize())
500
+
501
+ if init_func:
502
+ return await asyncio.wait_for(init_func(host, port, reader_tgt, writer_tgt),
503
+ timeout=config.TG_CONNECT_TIMEOUT)
504
+ return reader_tgt, writer_tgt
505
+
506
+ def is_conn_dead(self, reader, writer):
507
+ if writer.transport.is_closing():
508
+ return True
509
+ raw_reader = reader
510
+ while hasattr(raw_reader, 'upstream'):
511
+ raw_reader = raw_reader.upstream
512
+ if raw_reader.at_eof():
513
+ return True
514
+ return False
515
+
516
+ def register_host_port(self, host, port, init_func):
517
+ if (host, port, init_func) not in self.pools:
518
+ self.pools[(host, port, init_func)] = []
519
+
520
+ while len(self.pools[(host, port, init_func)]) < TgConnectionPool.MAX_CONNS_IN_POOL:
521
+ connect_task = asyncio.ensure_future(self.open_tg_connection(host, port, init_func))
522
+ self.pools[(host, port, init_func)].append(connect_task)
523
+
524
+ async def get_connection(self, host, port, init_func=None):
525
+ self.register_host_port(host, port, init_func)
526
+
527
+ ret = None
528
+ for task in self.pools[(host, port, init_func)][:]:
529
+ if task.done():
530
+ if task.exception():
531
+ self.pools[(host, port, init_func)].remove(task)
532
+ continue
533
+
534
+ reader, writer, *other = task.result()
535
+ if self.is_conn_dead(reader, writer):
536
+ self.pools[(host, port, init_func)].remove(task)
537
+ writer.transport.abort()
538
+ continue
539
+
540
+ if not ret:
541
+ self.pools[(host, port, init_func)].remove(task)
542
+ ret = (reader, writer, *other)
543
+
544
+ self.register_host_port(host, port, init_func)
545
+ if ret:
546
+ return ret
547
+ return await self.open_tg_connection(host, port, init_func)
548
+
549
+
550
+ tg_connection_pool = TgConnectionPool()
551
+
552
+
553
+ class LayeredStreamReaderBase:
554
+ __slots__ = ("upstream", )
555
+
556
+ def __init__(self, upstream):
557
+ self.upstream = upstream
558
+
559
+ async def read(self, n):
560
+ return await self.upstream.read(n)
561
+
562
+ async def readexactly(self, n):
563
+ return await self.upstream.readexactly(n)
564
+
565
+
566
+ class LayeredStreamWriterBase:
567
+ __slots__ = ("upstream", )
568
+
569
+ def __init__(self, upstream):
570
+ self.upstream = upstream
571
+
572
+ def write(self, data, extra={}):
573
+ return self.upstream.write(data)
574
+
575
+ def write_eof(self):
576
+ return self.upstream.write_eof()
577
+
578
+ async def drain(self):
579
+ return await self.upstream.drain()
580
+
581
+ def close(self):
582
+ return self.upstream.close()
583
+
584
+ def abort(self):
585
+ return self.upstream.transport.abort()
586
+
587
+ def get_extra_info(self, name):
588
+ return self.upstream.get_extra_info(name)
589
+
590
+ @property
591
+ def transport(self):
592
+ return self.upstream.transport
593
+
594
+
595
+ class FakeTLSStreamReader(LayeredStreamReaderBase):
596
+ __slots__ = ('buf', )
597
+
598
+ def __init__(self, upstream):
599
+ self.upstream = upstream
600
+ self.buf = bytearray()
601
+
602
+ async def read(self, n, ignore_buf=False):
603
+ if self.buf and not ignore_buf:
604
+ data = self.buf
605
+ self.buf = bytearray()
606
+ return bytes(data)
607
+
608
+ while True:
609
+ tls_rec_type = await self.upstream.readexactly(1)
610
+ if not tls_rec_type:
611
+ return b""
612
+
613
+ if tls_rec_type not in [b"\x14", b"\x17"]:
614
+ print_err("BUG: bad tls type %s in FakeTLSStreamReader" % tls_rec_type)
615
+ return b""
616
+
617
+ version = await self.upstream.readexactly(2)
618
+ if version != b"\x03\x03":
619
+ print_err("BUG: unknown version %s in FakeTLSStreamReader" % version)
620
+ return b""
621
+
622
+ data_len = int.from_bytes(await self.upstream.readexactly(2), "big")
623
+ data = await self.upstream.readexactly(data_len)
624
+ if tls_rec_type == b"\x14":
625
+ continue
626
+ return data
627
+
628
+ async def readexactly(self, n):
629
+ while len(self.buf) < n:
630
+ tls_data = await self.read(1, ignore_buf=True)
631
+ if not tls_data:
632
+ return b""
633
+ self.buf += tls_data
634
+ data, self.buf = self.buf[:n], self.buf[n:]
635
+ return bytes(data)
636
+
637
+
638
+ class FakeTLSStreamWriter(LayeredStreamWriterBase):
639
+ __slots__ = ()
640
+
641
+ def __init__(self, upstream):
642
+ self.upstream = upstream
643
+
644
+ def write(self, data, extra={}):
645
+ MAX_CHUNK_SIZE = 16384 + 24
646
+ for start in range(0, len(data), MAX_CHUNK_SIZE):
647
+ end = min(start+MAX_CHUNK_SIZE, len(data))
648
+ self.upstream.write(b"\x17\x03\x03" + int.to_bytes(end-start, 2, "big"))
649
+ self.upstream.write(data[start: end])
650
+ return len(data)
651
+
652
+
653
+ class CryptoWrappedStreamReader(LayeredStreamReaderBase):
654
+ __slots__ = ('decryptor', 'block_size', 'buf')
655
+
656
+ def __init__(self, upstream, decryptor, block_size=1):
657
+ self.upstream = upstream
658
+ self.decryptor = decryptor
659
+ self.block_size = block_size
660
+ self.buf = bytearray()
661
+
662
+ async def read(self, n):
663
+ if self.buf:
664
+ ret = bytes(self.buf)
665
+ self.buf.clear()
666
+ return ret
667
+ else:
668
+ data = await self.upstream.read(n)
669
+ if not data:
670
+ return b""
671
+
672
+ needed_till_full_block = -len(data) % self.block_size
673
+ if needed_till_full_block > 0:
674
+ data += await self.upstream.readexactly(needed_till_full_block)
675
+ return self.decryptor.decrypt(data)
676
+
677
+ async def readexactly(self, n):
678
+ if n > len(self.buf):
679
+ to_read = n - len(self.buf)
680
+ needed_till_full_block = -to_read % self.block_size
681
+
682
+ to_read_block_aligned = to_read + needed_till_full_block
683
+ data = await self.upstream.readexactly(to_read_block_aligned)
684
+ self.buf += self.decryptor.decrypt(data)
685
+
686
+ ret = bytes(self.buf[:n])
687
+ self.buf = self.buf[n:]
688
+ return ret
689
+
690
+
691
+ class CryptoWrappedStreamWriter(LayeredStreamWriterBase):
692
+ __slots__ = ('encryptor', 'block_size')
693
+
694
+ def __init__(self, upstream, encryptor, block_size=1):
695
+ self.upstream = upstream
696
+ self.encryptor = encryptor
697
+ self.block_size = block_size
698
+
699
+ def write(self, data, extra={}):
700
+ if len(data) % self.block_size != 0:
701
+ print_err("BUG: writing %d bytes not aligned to block size %d" % (
702
+ len(data), self.block_size))
703
+ return 0
704
+ q = self.encryptor.encrypt(data)
705
+ return self.upstream.write(q)
706
+
707
+
708
+ class MTProtoFrameStreamReader(LayeredStreamReaderBase):
709
+ __slots__ = ('seq_no', )
710
+
711
+ def __init__(self, upstream, seq_no=0):
712
+ self.upstream = upstream
713
+ self.seq_no = seq_no
714
+
715
+ async def read(self, buf_size):
716
+ msg_len_bytes = await self.upstream.readexactly(4)
717
+ msg_len = int.from_bytes(msg_len_bytes, "little")
718
+ # skip paddings
719
+ while msg_len == 4:
720
+ msg_len_bytes = await self.upstream.readexactly(4)
721
+ msg_len = int.from_bytes(msg_len_bytes, "little")
722
+
723
+ len_is_bad = (msg_len % len(PADDING_FILLER) != 0)
724
+ if not MIN_MSG_LEN <= msg_len <= MAX_MSG_LEN or len_is_bad:
725
+ print_err("msg_len is bad, closing connection", msg_len)
726
+ return b""
727
+
728
+ msg_seq_bytes = await self.upstream.readexactly(4)
729
+ msg_seq = int.from_bytes(msg_seq_bytes, "little", signed=True)
730
+ if msg_seq != self.seq_no:
731
+ print_err("unexpected seq_no")
732
+ return b""
733
+
734
+ self.seq_no += 1
735
+
736
+ data = await self.upstream.readexactly(msg_len - 4 - 4 - 4)
737
+ checksum_bytes = await self.upstream.readexactly(4)
738
+ checksum = int.from_bytes(checksum_bytes, "little")
739
+
740
+ computed_checksum = binascii.crc32(msg_len_bytes + msg_seq_bytes + data)
741
+ if computed_checksum != checksum:
742
+ return b""
743
+ return data
744
+
745
+
746
+ class MTProtoFrameStreamWriter(LayeredStreamWriterBase):
747
+ __slots__ = ('seq_no', )
748
+
749
+ def __init__(self, upstream, seq_no=0):
750
+ self.upstream = upstream
751
+ self.seq_no = seq_no
752
+
753
+ def write(self, msg, extra={}):
754
+ len_bytes = int.to_bytes(len(msg) + 4 + 4 + 4, 4, "little")
755
+ seq_bytes = int.to_bytes(self.seq_no, 4, "little", signed=True)
756
+ self.seq_no += 1
757
+
758
+ msg_without_checksum = len_bytes + seq_bytes + msg
759
+ checksum = int.to_bytes(binascii.crc32(msg_without_checksum), 4, "little")
760
+
761
+ full_msg = msg_without_checksum + checksum
762
+ padding = PADDING_FILLER * ((-len(full_msg) % CBC_PADDING) // len(PADDING_FILLER))
763
+
764
+ return self.upstream.write(full_msg + padding)
765
+
766
+
767
+ class MTProtoCompactFrameStreamReader(LayeredStreamReaderBase):
768
+ __slots__ = ()
769
+
770
+ async def read(self, buf_size):
771
+ msg_len_bytes = await self.upstream.readexactly(1)
772
+ msg_len = int.from_bytes(msg_len_bytes, "little")
773
+
774
+ extra = {"QUICKACK_FLAG": False}
775
+ if msg_len >= 0x80:
776
+ extra["QUICKACK_FLAG"] = True
777
+ msg_len -= 0x80
778
+
779
+ if msg_len == 0x7f:
780
+ msg_len_bytes = await self.upstream.readexactly(3)
781
+ msg_len = int.from_bytes(msg_len_bytes, "little")
782
+
783
+ msg_len *= 4
784
+
785
+ data = await self.upstream.readexactly(msg_len)
786
+
787
+ return data, extra
788
+
789
+
790
+ class MTProtoCompactFrameStreamWriter(LayeredStreamWriterBase):
791
+ __slots__ = ()
792
+
793
+ def write(self, data, extra={}):
794
+ SMALL_PKT_BORDER = 0x7f
795
+ LARGE_PKT_BORDER = 256 ** 3
796
+
797
+ if len(data) % 4 != 0:
798
+ print_err("BUG: MTProtoFrameStreamWriter attempted to send msg with len", len(data))
799
+ return 0
800
+
801
+ if extra.get("SIMPLE_ACK"):
802
+ return self.upstream.write(data[::-1])
803
+
804
+ len_div_four = len(data) // 4
805
+
806
+ if len_div_four < SMALL_PKT_BORDER:
807
+ return self.upstream.write(bytes([len_div_four]) + data)
808
+ elif len_div_four < LARGE_PKT_BORDER:
809
+ return self.upstream.write(b'\x7f' + int.to_bytes(len_div_four, 3, 'little') + data)
810
+ else:
811
+ print_err("Attempted to send too large pkt len =", len(data))
812
+ return 0
813
+
814
+
815
+ class MTProtoIntermediateFrameStreamReader(LayeredStreamReaderBase):
816
+ __slots__ = ()
817
+
818
+ async def read(self, buf_size):
819
+ msg_len_bytes = await self.upstream.readexactly(4)
820
+ msg_len = int.from_bytes(msg_len_bytes, "little")
821
+
822
+ extra = {}
823
+ if msg_len > 0x80000000:
824
+ extra["QUICKACK_FLAG"] = True
825
+ msg_len -= 0x80000000
826
+
827
+ data = await self.upstream.readexactly(msg_len)
828
+ return data, extra
829
+
830
+
831
+ class MTProtoIntermediateFrameStreamWriter(LayeredStreamWriterBase):
832
+ __slots__ = ()
833
+
834
+ def write(self, data, extra={}):
835
+ if extra.get("SIMPLE_ACK"):
836
+ return self.upstream.write(data)
837
+ else:
838
+ return self.upstream.write(int.to_bytes(len(data), 4, 'little') + data)
839
+
840
+
841
+ class MTProtoSecureIntermediateFrameStreamReader(LayeredStreamReaderBase):
842
+ __slots__ = ()
843
+
844
+ async def read(self, buf_size):
845
+ msg_len_bytes = await self.upstream.readexactly(4)
846
+ msg_len = int.from_bytes(msg_len_bytes, "little")
847
+
848
+ extra = {}
849
+ if msg_len > 0x80000000:
850
+ extra["QUICKACK_FLAG"] = True
851
+ msg_len -= 0x80000000
852
+
853
+ data = await self.upstream.readexactly(msg_len)
854
+
855
+ if msg_len % 4 != 0:
856
+ cut_border = msg_len - (msg_len % 4)
857
+ data = data[:cut_border]
858
+
859
+ return data, extra
860
+
861
+
862
+ class MTProtoSecureIntermediateFrameStreamWriter(LayeredStreamWriterBase):
863
+ __slots__ = ()
864
+
865
+ def write(self, data, extra={}):
866
+ MAX_PADDING_LEN = 4
867
+ if extra.get("SIMPLE_ACK"):
868
+ # TODO: make this unpredictable
869
+ return self.upstream.write(data)
870
+ else:
871
+ padding_len = myrandom.randrange(MAX_PADDING_LEN)
872
+ padding = myrandom.getrandbytes(padding_len)
873
+ padded_data_len_bytes = int.to_bytes(len(data) + padding_len, 4, 'little')
874
+ return self.upstream.write(padded_data_len_bytes + data + padding)
875
+
876
+
877
+ class ProxyReqStreamReader(LayeredStreamReaderBase):
878
+ __slots__ = ()
879
+
880
+ async def read(self, msg):
881
+ RPC_PROXY_ANS = b"\x0d\xda\x03\x44"
882
+ RPC_CLOSE_EXT = b"\xa2\x34\xb6\x5e"
883
+ RPC_SIMPLE_ACK = b"\x9b\x40\xac\x3b"
884
+ RPC_UNKNOWN = b'\xdf\xa2\x30\x57'
885
+
886
+ data = await self.upstream.read(1)
887
+
888
+ if len(data) < 4:
889
+ return b""
890
+
891
+ ans_type = data[:4]
892
+ if ans_type == RPC_CLOSE_EXT:
893
+ return b""
894
+
895
+ if ans_type == RPC_PROXY_ANS:
896
+ ans_flags, conn_id, conn_data = data[4:8], data[8:16], data[16:]
897
+ return conn_data
898
+
899
+ if ans_type == RPC_SIMPLE_ACK:
900
+ conn_id, confirm = data[4:12], data[12:16]
901
+ return confirm, {"SIMPLE_ACK": True}
902
+
903
+ if ans_type == RPC_UNKNOWN:
904
+ return b"", {"SKIP_SEND": True}
905
+
906
+ print_err("unknown rpc ans type:", ans_type)
907
+ return b"", {"SKIP_SEND": True}
908
+
909
+
910
+ class ProxyReqStreamWriter(LayeredStreamWriterBase):
911
+ __slots__ = ('remote_ip_port', 'our_ip_port', 'out_conn_id', 'proto_tag')
912
+
913
+ def __init__(self, upstream, cl_ip, cl_port, my_ip, my_port, proto_tag):
914
+ self.upstream = upstream
915
+
916
+ if ":" not in cl_ip:
917
+ self.remote_ip_port = b"\x00" * 10 + b"\xff\xff"
918
+ self.remote_ip_port += socket.inet_pton(socket.AF_INET, cl_ip)
919
+ else:
920
+ self.remote_ip_port = socket.inet_pton(socket.AF_INET6, cl_ip)
921
+ self.remote_ip_port += int.to_bytes(cl_port, 4, "little")
922
+
923
+ if ":" not in my_ip:
924
+ self.our_ip_port = b"\x00" * 10 + b"\xff\xff"
925
+ self.our_ip_port += socket.inet_pton(socket.AF_INET, my_ip)
926
+ else:
927
+ self.our_ip_port = socket.inet_pton(socket.AF_INET6, my_ip)
928
+ self.our_ip_port += int.to_bytes(my_port, 4, "little")
929
+ self.out_conn_id = myrandom.getrandbytes(8)
930
+
931
+ self.proto_tag = proto_tag
932
+
933
+ def write(self, msg, extra={}):
934
+ RPC_PROXY_REQ = b"\xee\xf1\xce\x36"
935
+ EXTRA_SIZE = b"\x18\x00\x00\x00"
936
+ PROXY_TAG = b"\xae\x26\x1e\xdb"
937
+ FOUR_BYTES_ALIGNER = b"\x00\x00\x00"
938
+
939
+ FLAG_NOT_ENCRYPTED = 0x2
940
+ FLAG_HAS_AD_TAG = 0x8
941
+ FLAG_MAGIC = 0x1000
942
+ FLAG_EXTMODE2 = 0x20000
943
+ FLAG_PAD = 0x8000000
944
+ FLAG_INTERMEDIATE = 0x20000000
945
+ FLAG_ABRIDGED = 0x40000000
946
+ FLAG_QUICKACK = 0x80000000
947
+
948
+ if len(msg) % 4 != 0:
949
+ print_err("BUG: attempted to send msg with len %d" % len(msg))
950
+ return 0
951
+
952
+ flags = FLAG_MAGIC | FLAG_EXTMODE2
953
+
954
+ if self.proto_tag == PROTO_TAG_ABRIDGED:
955
+ flags |= FLAG_ABRIDGED
956
+ elif self.proto_tag == PROTO_TAG_INTERMEDIATE:
957
+ flags |= FLAG_INTERMEDIATE
958
+ elif self.proto_tag == PROTO_TAG_SECURE:
959
+ flags |= FLAG_INTERMEDIATE | FLAG_PAD
960
+
961
+ if extra.get("QUICKACK_FLAG"):
962
+ flags |= FLAG_QUICKACK
963
+
964
+ if msg.startswith(b"\x00" * 8):
965
+ flags |= FLAG_NOT_ENCRYPTED
966
+
967
+ if len(config.AD_TAG) == 16:
968
+ flags |= FLAG_HAS_AD_TAG
969
+
970
+ full_msg = bytearray()
971
+ full_msg += RPC_PROXY_REQ + int.to_bytes(flags, 4, "little") + self.out_conn_id
972
+ full_msg += self.remote_ip_port + self.our_ip_port
973
+ if len(config.AD_TAG) == 16:
974
+ full_msg += EXTRA_SIZE + PROXY_TAG
975
+ full_msg += bytes([len(config.AD_TAG)]) + config.AD_TAG + FOUR_BYTES_ALIGNER
976
+ full_msg += msg
977
+
978
+ return self.upstream.write(full_msg)
979
+
980
+
981
+ def try_setsockopt(sock, level, option, value):
982
+ try:
983
+ sock.setsockopt(level, option, value)
984
+ except OSError as E:
985
+ pass
986
+
987
+
988
+ def set_keepalive(sock, interval=40, attempts=5):
989
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
990
+ if hasattr(socket, "TCP_KEEPIDLE"):
991
+ try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, interval)
992
+ if hasattr(socket, "TCP_KEEPINTVL"):
993
+ try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval)
994
+ if hasattr(socket, "TCP_KEEPCNT"):
995
+ try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_KEEPCNT, attempts)
996
+
997
+
998
+ def set_ack_timeout(sock, timeout):
999
+ if hasattr(socket, "TCP_USER_TIMEOUT"):
1000
+ try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, timeout*1000)
1001
+
1002
+
1003
+ def set_bufsizes(sock, recv_buf, send_buf):
1004
+ try_setsockopt(sock, socket.SOL_SOCKET, socket.SO_RCVBUF, recv_buf)
1005
+ try_setsockopt(sock, socket.SOL_SOCKET, socket.SO_SNDBUF, send_buf)
1006
+
1007
+
1008
+ def set_instant_rst(sock):
1009
+ INSTANT_RST = b"\x01\x00\x00\x00\x00\x00\x00\x00"
1010
+ if hasattr(socket, "SO_LINGER"):
1011
+ try_setsockopt(sock, socket.SOL_SOCKET, socket.SO_LINGER, INSTANT_RST)
1012
+
1013
+
1014
+ def gen_x25519_public_key():
1015
+ # generates some number which has square root by modulo P
1016
+ P = 2**255 - 19
1017
+ n = myrandom.randrange(P)
1018
+ return int.to_bytes((n*n) % P, length=32, byteorder="little")
1019
+
1020
+
1021
+ async def connect_reader_to_writer(reader, writer):
1022
+ BUF_SIZE = 8192
1023
+ try:
1024
+ while True:
1025
+ data = await reader.read(BUF_SIZE)
1026
+
1027
+ if not data:
1028
+ if not writer.transport.is_closing():
1029
+ writer.write_eof()
1030
+ await writer.drain()
1031
+ return
1032
+
1033
+ writer.write(data)
1034
+ await writer.drain()
1035
+ except (OSError, asyncio.IncompleteReadError) as e:
1036
+ pass
1037
+
1038
+
1039
+ async def handle_bad_client(reader_clt, writer_clt, handshake):
1040
+ BUF_SIZE = 8192
1041
+ CONNECT_TIMEOUT = 5
1042
+
1043
+ global mask_host_cached_ip
1044
+
1045
+ update_stats(connects_bad=1)
1046
+
1047
+ if writer_clt.transport.is_closing():
1048
+ return
1049
+
1050
+ set_bufsizes(writer_clt.get_extra_info("socket"), BUF_SIZE, BUF_SIZE)
1051
+
1052
+ if not config.MASK or handshake is None:
1053
+ while await reader_clt.read(BUF_SIZE):
1054
+ # just consume all the data
1055
+ pass
1056
+ return
1057
+
1058
+ writer_srv = None
1059
+ try:
1060
+ host = mask_host_cached_ip or config.MASK_HOST
1061
+ task = asyncio.open_connection(host, config.MASK_PORT, limit=BUF_SIZE)
1062
+ reader_srv, writer_srv = await asyncio.wait_for(task, timeout=CONNECT_TIMEOUT)
1063
+ if not mask_host_cached_ip:
1064
+ mask_host_cached_ip = writer_srv.get_extra_info("peername")[0]
1065
+ writer_srv.write(handshake)
1066
+ await writer_srv.drain()
1067
+
1068
+ srv_to_clt = connect_reader_to_writer(reader_srv, writer_clt)
1069
+ clt_to_srv = connect_reader_to_writer(reader_clt, writer_srv)
1070
+ task_srv_to_clt = asyncio.ensure_future(srv_to_clt)
1071
+ task_clt_to_srv = asyncio.ensure_future(clt_to_srv)
1072
+
1073
+ await asyncio.wait([task_srv_to_clt, task_clt_to_srv], return_when=asyncio.FIRST_COMPLETED)
1074
+
1075
+ task_srv_to_clt.cancel()
1076
+ task_clt_to_srv.cancel()
1077
+
1078
+ if writer_clt.transport.is_closing():
1079
+ return
1080
+
1081
+ # if the server closed the connection with RST or FIN-RST, copy them to the client
1082
+ if not writer_srv.transport.is_closing():
1083
+ # workaround for uvloop, it doesn't fire exceptions on write_eof
1084
+ sock = writer_srv.get_extra_info('socket')
1085
+ raw_sock = socket.socket(sock.family, sock.type, sock.proto, sock.fileno())
1086
+ try:
1087
+ raw_sock.shutdown(socket.SHUT_WR)
1088
+ except OSError as E:
1089
+ set_instant_rst(writer_clt.get_extra_info("socket"))
1090
+ finally:
1091
+ raw_sock.detach()
1092
+ else:
1093
+ set_instant_rst(writer_clt.get_extra_info("socket"))
1094
+ except ConnectionRefusedError as E:
1095
+ return
1096
+ except (OSError, asyncio.TimeoutError) as E:
1097
+ return
1098
+ finally:
1099
+ if writer_srv is not None:
1100
+ writer_srv.transport.abort()
1101
+
1102
+
1103
+ async def handle_fake_tls_handshake(handshake, reader, writer, peer):
1104
+ global used_handshakes
1105
+ global client_ips
1106
+ global last_client_ips
1107
+ global last_clients_with_time_skew
1108
+ global last_clients_with_same_handshake
1109
+ global fake_cert_len
1110
+
1111
+ TIME_SKEW_MIN = -20 * 60
1112
+ TIME_SKEW_MAX = 10 * 60
1113
+
1114
+ TLS_VERS = b"\x03\x03"
1115
+ TLS_CIPHERSUITE = b"\x13\x01"
1116
+ TLS_CHANGE_CIPHER = b"\x14" + TLS_VERS + b"\x00\x01\x01"
1117
+ TLS_APP_HTTP2_HDR = b"\x17" + TLS_VERS
1118
+
1119
+ DIGEST_LEN = 32
1120
+ DIGEST_HALFLEN = 16
1121
+ DIGEST_POS = 11
1122
+
1123
+ SESSION_ID_LEN_POS = DIGEST_POS + DIGEST_LEN
1124
+ SESSION_ID_POS = SESSION_ID_LEN_POS + 1
1125
+
1126
+ tls_extensions = b"\x00\x2e" + b"\x00\x33\x00\x24" + b"\x00\x1d\x00\x20"
1127
+ tls_extensions += gen_x25519_public_key() + b"\x00\x2b\x00\x02\x03\x04"
1128
+
1129
+ digest = handshake[DIGEST_POS:DIGEST_POS+DIGEST_LEN]
1130
+
1131
+ if digest[:DIGEST_HALFLEN] in used_handshakes:
1132
+ last_clients_with_same_handshake[peer[0]] += 1
1133
+ return False
1134
+
1135
+ sess_id_len = handshake[SESSION_ID_LEN_POS]
1136
+ sess_id = handshake[SESSION_ID_POS:SESSION_ID_POS+sess_id_len]
1137
+
1138
+ for user in config.USERS:
1139
+ secret = bytes.fromhex(config.USERS[user])
1140
+
1141
+ msg = handshake[:DIGEST_POS] + b"\x00"*DIGEST_LEN + handshake[DIGEST_POS+DIGEST_LEN:]
1142
+ computed_digest = hmac.new(secret, msg, digestmod=hashlib.sha256).digest()
1143
+
1144
+ xored_digest = bytes(digest[i] ^ computed_digest[i] for i in range(DIGEST_LEN))
1145
+ digest_good = xored_digest.startswith(b"\x00" * (DIGEST_LEN-4))
1146
+
1147
+ if not digest_good:
1148
+ continue
1149
+
1150
+ timestamp = int.from_bytes(xored_digest[-4:], "little")
1151
+ client_time_is_ok = TIME_SKEW_MIN < time.time() - timestamp < TIME_SKEW_MAX
1152
+
1153
+ # some clients fail to read unix time and send the time since boot instead
1154
+ client_time_is_small = timestamp < 60*60*24*1000
1155
+ accept_bad_time = config.IGNORE_TIME_SKEW or is_time_skewed or client_time_is_small
1156
+
1157
+ if not client_time_is_ok and not accept_bad_time:
1158
+ last_clients_with_time_skew[peer[0]] = (time.time() - timestamp) // 60
1159
+ continue
1160
+
1161
+ http_data = myrandom.getrandbytes(fake_cert_len)
1162
+
1163
+ srv_hello = TLS_VERS + b"\x00"*DIGEST_LEN + bytes([sess_id_len]) + sess_id
1164
+ srv_hello += TLS_CIPHERSUITE + b"\x00" + tls_extensions
1165
+
1166
+ hello_pkt = b"\x16" + TLS_VERS + int.to_bytes(len(srv_hello) + 4, 2, "big")
1167
+ hello_pkt += b"\x02" + int.to_bytes(len(srv_hello), 3, "big") + srv_hello
1168
+ hello_pkt += TLS_CHANGE_CIPHER + TLS_APP_HTTP2_HDR
1169
+ hello_pkt += int.to_bytes(len(http_data), 2, "big") + http_data
1170
+
1171
+ computed_digest = hmac.new(secret, msg=digest+hello_pkt, digestmod=hashlib.sha256).digest()
1172
+ hello_pkt = hello_pkt[:DIGEST_POS] + computed_digest + hello_pkt[DIGEST_POS+DIGEST_LEN:]
1173
+
1174
+ writer.write(hello_pkt)
1175
+ await writer.drain()
1176
+
1177
+ if config.REPLAY_CHECK_LEN > 0:
1178
+ while len(used_handshakes) >= config.REPLAY_CHECK_LEN:
1179
+ used_handshakes.popitem(last=False)
1180
+ used_handshakes[digest[:DIGEST_HALFLEN]] = True
1181
+
1182
+ if config.CLIENT_IPS_LEN > 0:
1183
+ while len(client_ips) >= config.CLIENT_IPS_LEN:
1184
+ client_ips.popitem(last=False)
1185
+ if peer[0] not in client_ips:
1186
+ client_ips[peer[0]] = True
1187
+ last_client_ips[peer[0]] = True
1188
+
1189
+ reader = FakeTLSStreamReader(reader)
1190
+ writer = FakeTLSStreamWriter(writer)
1191
+ return reader, writer
1192
+
1193
+ return False
1194
+
1195
+
1196
+ async def handle_proxy_protocol(reader, peer=None):
1197
+ PROXY_SIGNATURE = b"PROXY "
1198
+ PROXY_MIN_LEN = 6
1199
+ PROXY_TCP4 = b"TCP4"
1200
+ PROXY_TCP6 = b"TCP6"
1201
+ PROXY_UNKNOWN = b"UNKNOWN"
1202
+
1203
+ PROXY2_SIGNATURE = b"\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a"
1204
+ PROXY2_MIN_LEN = 16
1205
+ PROXY2_AF_UNSPEC = 0x0
1206
+ PROXY2_AF_INET = 0x1
1207
+ PROXY2_AF_INET6 = 0x2
1208
+
1209
+ header = await reader.readexactly(PROXY_MIN_LEN)
1210
+ if header.startswith(PROXY_SIGNATURE):
1211
+ # proxy header v1
1212
+ header += await reader.readuntil(b"\r\n")
1213
+ _, proxy_fam, *proxy_addr = header[:-2].split(b" ")
1214
+ if proxy_fam in (PROXY_TCP4, PROXY_TCP6):
1215
+ if len(proxy_addr) == 4:
1216
+ src_addr = proxy_addr[0].decode('ascii')
1217
+ src_port = int(proxy_addr[2].decode('ascii'))
1218
+ return (src_addr, src_port)
1219
+ elif proxy_fam == PROXY_UNKNOWN:
1220
+ return peer
1221
+ return False
1222
+
1223
+ header += await reader.readexactly(PROXY2_MIN_LEN - PROXY_MIN_LEN)
1224
+ if header.startswith(PROXY2_SIGNATURE):
1225
+ # proxy header v2
1226
+ proxy_ver = header[12]
1227
+ if proxy_ver & 0xf0 != 0x20:
1228
+ return False
1229
+ proxy_len = int.from_bytes(header[14:16], "big")
1230
+ proxy_addr = await reader.readexactly(proxy_len)
1231
+ if proxy_ver == 0x21:
1232
+ proxy_fam = header[13] >> 4
1233
+ if proxy_fam == PROXY2_AF_INET:
1234
+ if proxy_len >= (4 + 2)*2:
1235
+ src_addr = socket.inet_ntop(socket.AF_INET, proxy_addr[:4])
1236
+ src_port = int.from_bytes(proxy_addr[8:10], "big")
1237
+ return (src_addr, src_port)
1238
+ elif proxy_fam == PROXY2_AF_INET6:
1239
+ if proxy_len >= (16 + 2)*2:
1240
+ src_addr = socket.inet_ntop(socket.AF_INET6, proxy_addr[:16])
1241
+ src_port = int.from_bytes(proxy_addr[32:34], "big")
1242
+ return (src_addr, src_port)
1243
+ elif proxy_fam == PROXY2_AF_UNSPEC:
1244
+ return peer
1245
+ elif proxy_ver == 0x20:
1246
+ return peer
1247
+
1248
+ return False
1249
+
1250
+
1251
+ async def handle_handshake(reader, writer):
1252
+ global used_handshakes
1253
+ global client_ips
1254
+ global last_client_ips
1255
+ global last_clients_with_same_handshake
1256
+
1257
+ TLS_START_BYTES = b"\x16\x03\x01"
1258
+
1259
+ if writer.transport.is_closing() or writer.get_extra_info("peername") is None:
1260
+ return False
1261
+
1262
+ peer = writer.get_extra_info("peername")[:2]
1263
+ if not peer:
1264
+ peer = ("unknown ip", 0)
1265
+
1266
+ if config.PROXY_PROTOCOL:
1267
+ ip = peer[0] if peer else "unknown ip"
1268
+ peer = await handle_proxy_protocol(reader, peer)
1269
+ if not peer:
1270
+ print_err("Client from %s sent bad proxy protocol headers" % ip)
1271
+ await handle_bad_client(reader, writer, None)
1272
+ return False
1273
+
1274
+ is_tls_handshake = True
1275
+ handshake = b""
1276
+ for expected_byte in TLS_START_BYTES:
1277
+ handshake += await reader.readexactly(1)
1278
+ if handshake[-1] != expected_byte:
1279
+ is_tls_handshake = False
1280
+ break
1281
+
1282
+ if is_tls_handshake:
1283
+ handshake += await reader.readexactly(2)
1284
+ tls_handshake_len = int.from_bytes(handshake[-2:], "big")
1285
+ if tls_handshake_len < 512:
1286
+ is_tls_handshake = False
1287
+
1288
+ if is_tls_handshake:
1289
+ handshake += await reader.readexactly(tls_handshake_len)
1290
+ tls_handshake_result = await handle_fake_tls_handshake(handshake, reader, writer, peer)
1291
+
1292
+ if not tls_handshake_result:
1293
+ await handle_bad_client(reader, writer, handshake)
1294
+ return False
1295
+ reader, writer = tls_handshake_result
1296
+ handshake = await reader.readexactly(HANDSHAKE_LEN)
1297
+ else:
1298
+ if not config.MODES["classic"] and not config.MODES["secure"]:
1299
+ await handle_bad_client(reader, writer, handshake)
1300
+ return False
1301
+ handshake += await reader.readexactly(HANDSHAKE_LEN - len(handshake))
1302
+
1303
+ dec_prekey_and_iv = handshake[SKIP_LEN:SKIP_LEN+PREKEY_LEN+IV_LEN]
1304
+ dec_prekey, dec_iv = dec_prekey_and_iv[:PREKEY_LEN], dec_prekey_and_iv[PREKEY_LEN:]
1305
+ enc_prekey_and_iv = handshake[SKIP_LEN:SKIP_LEN+PREKEY_LEN+IV_LEN][::-1]
1306
+ enc_prekey, enc_iv = enc_prekey_and_iv[:PREKEY_LEN], enc_prekey_and_iv[PREKEY_LEN:]
1307
+
1308
+ if dec_prekey_and_iv in used_handshakes:
1309
+ last_clients_with_same_handshake[peer[0]] += 1
1310
+ await handle_bad_client(reader, writer, handshake)
1311
+ return False
1312
+
1313
+ for user in config.USERS:
1314
+ secret = bytes.fromhex(config.USERS[user])
1315
+
1316
+ dec_key = hashlib.sha256(dec_prekey + secret).digest()
1317
+ decryptor = create_aes_ctr(key=dec_key, iv=int.from_bytes(dec_iv, "big"))
1318
+
1319
+ enc_key = hashlib.sha256(enc_prekey + secret).digest()
1320
+ encryptor = create_aes_ctr(key=enc_key, iv=int.from_bytes(enc_iv, "big"))
1321
+
1322
+ decrypted = decryptor.decrypt(handshake)
1323
+
1324
+ proto_tag = decrypted[PROTO_TAG_POS:PROTO_TAG_POS+4]
1325
+ if proto_tag not in (PROTO_TAG_ABRIDGED, PROTO_TAG_INTERMEDIATE, PROTO_TAG_SECURE):
1326
+ continue
1327
+
1328
+ if proto_tag == PROTO_TAG_SECURE:
1329
+ if is_tls_handshake and not config.MODES["tls"]:
1330
+ continue
1331
+ if not is_tls_handshake and not config.MODES["secure"]:
1332
+ continue
1333
+ else:
1334
+ if not config.MODES["classic"]:
1335
+ continue
1336
+
1337
+ dc_idx = int.from_bytes(decrypted[DC_IDX_POS:DC_IDX_POS+2], "little", signed=True)
1338
+
1339
+ if config.REPLAY_CHECK_LEN > 0:
1340
+ while len(used_handshakes) >= config.REPLAY_CHECK_LEN:
1341
+ used_handshakes.popitem(last=False)
1342
+ used_handshakes[dec_prekey_and_iv] = True
1343
+
1344
+ if config.CLIENT_IPS_LEN > 0:
1345
+ while len(client_ips) >= config.CLIENT_IPS_LEN:
1346
+ client_ips.popitem(last=False)
1347
+ if peer[0] not in client_ips:
1348
+ client_ips[peer[0]] = True
1349
+ last_client_ips[peer[0]] = True
1350
+
1351
+ reader = CryptoWrappedStreamReader(reader, decryptor)
1352
+ writer = CryptoWrappedStreamWriter(writer, encryptor)
1353
+ return reader, writer, proto_tag, user, dc_idx, enc_key + enc_iv, peer
1354
+
1355
+ await handle_bad_client(reader, writer, handshake)
1356
+ return False
1357
+
1358
+
1359
+ async def do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=None):
1360
+ RESERVED_NONCE_FIRST_CHARS = [b"\xef"]
1361
+ RESERVED_NONCE_BEGININGS = [b"\x48\x45\x41\x44", b"\x50\x4F\x53\x54",
1362
+ b"\x47\x45\x54\x20", b"\xee\xee\xee\xee",
1363
+ b"\xdd\xdd\xdd\xdd", b"\x16\x03\x01\x02"]
1364
+ RESERVED_NONCE_CONTINUES = [b"\x00\x00\x00\x00"]
1365
+
1366
+ global my_ip_info
1367
+ global tg_connection_pool
1368
+
1369
+ dc_idx = abs(dc_idx) - 1
1370
+
1371
+ if my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"]):
1372
+ if not 0 <= dc_idx < len(TG_DATACENTERS_V6):
1373
+ return False
1374
+ dc = TG_DATACENTERS_V6[dc_idx]
1375
+ else:
1376
+ if not 0 <= dc_idx < len(TG_DATACENTERS_V4):
1377
+ return False
1378
+ dc = TG_DATACENTERS_V4[dc_idx]
1379
+
1380
+ try:
1381
+ reader_tgt, writer_tgt = await tg_connection_pool.get_connection(dc, TG_DATACENTER_PORT)
1382
+ except ConnectionRefusedError as E:
1383
+ print_err("Got connection refused while trying to connect to", dc, TG_DATACENTER_PORT)
1384
+ return False
1385
+ except ConnectionAbortedError as E:
1386
+ print_err("The Telegram server connection is bad: %d (%s %s) %s" % (dc_idx, dc, TG_DATACENTER_PORT, E))
1387
+ return False
1388
+ except (OSError, asyncio.TimeoutError) as E:
1389
+ print_err("Unable to connect to", dc, TG_DATACENTER_PORT)
1390
+ return False
1391
+
1392
+ while True:
1393
+ rnd = bytearray(myrandom.getrandbytes(HANDSHAKE_LEN))
1394
+ if rnd[:1] in RESERVED_NONCE_FIRST_CHARS:
1395
+ continue
1396
+ if rnd[:4] in RESERVED_NONCE_BEGININGS:
1397
+ continue
1398
+ if rnd[4:8] in RESERVED_NONCE_CONTINUES:
1399
+ continue
1400
+ break
1401
+
1402
+ rnd[PROTO_TAG_POS:PROTO_TAG_POS+4] = proto_tag
1403
+
1404
+ if dec_key_and_iv:
1405
+ rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN] = dec_key_and_iv[::-1]
1406
+
1407
+ rnd = bytes(rnd)
1408
+
1409
+ dec_key_and_iv = rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN][::-1]
1410
+ dec_key, dec_iv = dec_key_and_iv[:KEY_LEN], dec_key_and_iv[KEY_LEN:]
1411
+ decryptor = create_aes_ctr(key=dec_key, iv=int.from_bytes(dec_iv, "big"))
1412
+
1413
+ enc_key_and_iv = rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN]
1414
+ enc_key, enc_iv = enc_key_and_iv[:KEY_LEN], enc_key_and_iv[KEY_LEN:]
1415
+ encryptor = create_aes_ctr(key=enc_key, iv=int.from_bytes(enc_iv, "big"))
1416
+
1417
+ rnd_enc = rnd[:PROTO_TAG_POS] + encryptor.encrypt(rnd)[PROTO_TAG_POS:]
1418
+
1419
+ writer_tgt.write(rnd_enc)
1420
+ await writer_tgt.drain()
1421
+
1422
+ reader_tgt = CryptoWrappedStreamReader(reader_tgt, decryptor)
1423
+ writer_tgt = CryptoWrappedStreamWriter(writer_tgt, encryptor)
1424
+
1425
+ return reader_tgt, writer_tgt
1426
+
1427
+
1428
+ def get_middleproxy_aes_key_and_iv(nonce_srv, nonce_clt, clt_ts, srv_ip, clt_port, purpose,
1429
+ clt_ip, srv_port, middleproxy_secret, clt_ipv6=None,
1430
+ srv_ipv6=None):
1431
+ EMPTY_IP = b"\x00\x00\x00\x00"
1432
+
1433
+ if not clt_ip or not srv_ip:
1434
+ clt_ip = EMPTY_IP
1435
+ srv_ip = EMPTY_IP
1436
+
1437
+ s = bytearray()
1438
+ s += nonce_srv + nonce_clt + clt_ts + srv_ip + clt_port + purpose + clt_ip + srv_port
1439
+ s += middleproxy_secret + nonce_srv
1440
+
1441
+ if clt_ipv6 and srv_ipv6:
1442
+ s += clt_ipv6 + srv_ipv6
1443
+
1444
+ s += nonce_clt
1445
+
1446
+ md5_sum = hashlib.md5(s[1:]).digest()
1447
+ sha1_sum = hashlib.sha1(s).digest()
1448
+
1449
+ key = md5_sum[:12] + sha1_sum
1450
+ iv = hashlib.md5(s[2:]).digest()
1451
+ return key, iv
1452
+
1453
+
1454
+ async def middleproxy_handshake(host, port, reader_tgt, writer_tgt):
1455
+ """ The most logic of middleproxy handshake, launched in pool """
1456
+ START_SEQ_NO = -2
1457
+ NONCE_LEN = 16
1458
+
1459
+ RPC_HANDSHAKE = b"\xf5\xee\x82\x76"
1460
+ RPC_NONCE = b"\xaa\x87\xcb\x7a"
1461
+ # pass as consts to simplify code
1462
+ RPC_FLAGS = b"\x00\x00\x00\x00"
1463
+ CRYPTO_AES = b"\x01\x00\x00\x00"
1464
+
1465
+ RPC_NONCE_ANS_LEN = 32
1466
+ RPC_HANDSHAKE_ANS_LEN = 32
1467
+
1468
+ writer_tgt = MTProtoFrameStreamWriter(writer_tgt, START_SEQ_NO)
1469
+ key_selector = PROXY_SECRET[:4]
1470
+ crypto_ts = int.to_bytes(int(time.time()) % (256**4), 4, "little")
1471
+
1472
+ nonce = myrandom.getrandbytes(NONCE_LEN)
1473
+
1474
+ msg = RPC_NONCE + key_selector + CRYPTO_AES + crypto_ts + nonce
1475
+
1476
+ writer_tgt.write(msg)
1477
+ await writer_tgt.drain()
1478
+
1479
+ reader_tgt = MTProtoFrameStreamReader(reader_tgt, START_SEQ_NO)
1480
+ ans = await reader_tgt.read(get_to_clt_bufsize())
1481
+
1482
+ if len(ans) != RPC_NONCE_ANS_LEN:
1483
+ raise ConnectionAbortedError("bad rpc answer length")
1484
+
1485
+ rpc_type, rpc_key_selector, rpc_schema, rpc_crypto_ts, rpc_nonce = (
1486
+ ans[:4], ans[4:8], ans[8:12], ans[12:16], ans[16:32]
1487
+ )
1488
+
1489
+ if rpc_type != RPC_NONCE or rpc_key_selector != key_selector or rpc_schema != CRYPTO_AES:
1490
+ raise ConnectionAbortedError("bad rpc answer")
1491
+
1492
+ # get keys
1493
+ tg_ip, tg_port = writer_tgt.upstream.get_extra_info('peername')[:2]
1494
+ my_ip, my_port = writer_tgt.upstream.get_extra_info('sockname')[:2]
1495
+
1496
+ use_ipv6_tg = (":" in tg_ip)
1497
+
1498
+ if not use_ipv6_tg:
1499
+ if my_ip_info["ipv4"]:
1500
+ # prefer global ip settings to work behind NAT
1501
+ my_ip = my_ip_info["ipv4"]
1502
+
1503
+ tg_ip_bytes = socket.inet_pton(socket.AF_INET, tg_ip)[::-1]
1504
+ my_ip_bytes = socket.inet_pton(socket.AF_INET, my_ip)[::-1]
1505
+
1506
+ tg_ipv6_bytes = None
1507
+ my_ipv6_bytes = None
1508
+ else:
1509
+ if my_ip_info["ipv6"]:
1510
+ my_ip = my_ip_info["ipv6"]
1511
+
1512
+ tg_ip_bytes = None
1513
+ my_ip_bytes = None
1514
+
1515
+ tg_ipv6_bytes = socket.inet_pton(socket.AF_INET6, tg_ip)
1516
+ my_ipv6_bytes = socket.inet_pton(socket.AF_INET6, my_ip)
1517
+
1518
+ tg_port_bytes = int.to_bytes(tg_port, 2, "little")
1519
+ my_port_bytes = int.to_bytes(my_port, 2, "little")
1520
+
1521
+ enc_key, enc_iv = get_middleproxy_aes_key_and_iv(
1522
+ nonce_srv=rpc_nonce, nonce_clt=nonce, clt_ts=crypto_ts, srv_ip=tg_ip_bytes,
1523
+ clt_port=my_port_bytes, purpose=b"CLIENT", clt_ip=my_ip_bytes, srv_port=tg_port_bytes,
1524
+ middleproxy_secret=PROXY_SECRET, clt_ipv6=my_ipv6_bytes, srv_ipv6=tg_ipv6_bytes)
1525
+
1526
+ dec_key, dec_iv = get_middleproxy_aes_key_and_iv(
1527
+ nonce_srv=rpc_nonce, nonce_clt=nonce, clt_ts=crypto_ts, srv_ip=tg_ip_bytes,
1528
+ clt_port=my_port_bytes, purpose=b"SERVER", clt_ip=my_ip_bytes, srv_port=tg_port_bytes,
1529
+ middleproxy_secret=PROXY_SECRET, clt_ipv6=my_ipv6_bytes, srv_ipv6=tg_ipv6_bytes)
1530
+
1531
+ encryptor = create_aes_cbc(key=enc_key, iv=enc_iv)
1532
+ decryptor = create_aes_cbc(key=dec_key, iv=dec_iv)
1533
+
1534
+ SENDER_PID = b"IPIPPRPDTIME"
1535
+ PEER_PID = b"IPIPPRPDTIME"
1536
+
1537
+ # TODO: pass client ip and port here for statistics
1538
+ handshake = RPC_HANDSHAKE + RPC_FLAGS + SENDER_PID + PEER_PID
1539
+
1540
+ writer_tgt.upstream = CryptoWrappedStreamWriter(writer_tgt.upstream, encryptor, block_size=16)
1541
+ writer_tgt.write(handshake)
1542
+ await writer_tgt.drain()
1543
+
1544
+ reader_tgt.upstream = CryptoWrappedStreamReader(reader_tgt.upstream, decryptor, block_size=16)
1545
+
1546
+ handshake_ans = await reader_tgt.read(1)
1547
+ if len(handshake_ans) != RPC_HANDSHAKE_ANS_LEN:
1548
+ raise ConnectionAbortedError("bad rpc handshake answer length")
1549
+
1550
+ handshake_type, handshake_flags, handshake_sender_pid, handshake_peer_pid = (
1551
+ handshake_ans[:4], handshake_ans[4:8], handshake_ans[8:20], handshake_ans[20:32])
1552
+ if handshake_type != RPC_HANDSHAKE or handshake_peer_pid != SENDER_PID:
1553
+ raise ConnectionAbortedError("bad rpc handshake answer")
1554
+
1555
+ return reader_tgt, writer_tgt, my_ip, my_port
1556
+
1557
+
1558
+ async def do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port):
1559
+ global my_ip_info
1560
+ global tg_connection_pool
1561
+
1562
+ use_ipv6_tg = (my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"]))
1563
+
1564
+ if use_ipv6_tg:
1565
+ if dc_idx not in TG_MIDDLE_PROXIES_V6:
1566
+ return False
1567
+ proxies = list(TG_MIDDLE_PROXIES_V6[dc_idx])
1568
+ else:
1569
+ if dc_idx not in TG_MIDDLE_PROXIES_V4:
1570
+ return False
1571
+ proxies = list(TG_MIDDLE_PROXIES_V4[dc_idx])
1572
+
1573
+ myrandom.shuffle(proxies)
1574
+
1575
+ for addr, port in proxies:
1576
+ try:
1577
+ reader_tgt, writer_tgt, my_ip, my_port = await tg_connection_pool.get_connection(
1578
+ addr, port, middleproxy_handshake)
1579
+ break
1580
+ except ConnectionRefusedError as E:
1581
+ print_err("The Telegram server %d (%s %s) is refusing connections, trying next" % (dc_idx, addr, port))
1582
+ except ConnectionAbortedError as E:
1583
+ print_err("The Telegram server connection is bad: %d (%s %s) %s, trying next" % (dc_idx, addr, port, E))
1584
+ except (OSError, asyncio.TimeoutError) as E:
1585
+ print_err("Unable to connect to the Telegram server %d (%s %s), trying next" % (dc_idx, addr, port))
1586
+ else:
1587
+ return False
1588
+
1589
+ writer_tgt = ProxyReqStreamWriter(writer_tgt, cl_ip, cl_port, my_ip, my_port, proto_tag)
1590
+ reader_tgt = ProxyReqStreamReader(reader_tgt)
1591
+
1592
+ return reader_tgt, writer_tgt
1593
+
1594
+
1595
+ async def tg_connect_reader_to_writer(rd, wr, user, rd_buf_size, is_upstream):
1596
+ try:
1597
+ while True:
1598
+ if not is_upstream:
1599
+ data = await asyncio.wait_for(rd.read(rd_buf_size),
1600
+ timeout=config.TG_READ_TIMEOUT)
1601
+ else:
1602
+ data = await rd.read(rd_buf_size)
1603
+ if isinstance(data, tuple):
1604
+ data, extra = data
1605
+ else:
1606
+ extra = {}
1607
+
1608
+ if extra.get("SKIP_SEND"):
1609
+ continue
1610
+
1611
+ if not data:
1612
+ wr.write_eof()
1613
+ await wr.drain()
1614
+ return
1615
+ else:
1616
+ if is_upstream:
1617
+ update_user_stats(user, octets_from_client=len(data), msgs_from_client=1)
1618
+ else:
1619
+ update_user_stats(user, octets_to_client=len(data), msgs_to_client=1)
1620
+
1621
+ wr.write(data, extra)
1622
+ await wr.drain()
1623
+ except (OSError, asyncio.IncompleteReadError, asyncio.TimeoutError) as e:
1624
+ # print_err(e)
1625
+ pass
1626
+
1627
+
1628
+ async def handle_client(reader_clt, writer_clt):
1629
+ set_keepalive(writer_clt.get_extra_info("socket"), config.CLIENT_KEEPALIVE, attempts=3)
1630
+ set_ack_timeout(writer_clt.get_extra_info("socket"), config.CLIENT_ACK_TIMEOUT)
1631
+ set_bufsizes(writer_clt.get_extra_info("socket"), get_to_tg_bufsize(), get_to_clt_bufsize())
1632
+
1633
+ update_stats(connects_all=1)
1634
+
1635
+ try:
1636
+ clt_data = await asyncio.wait_for(handle_handshake(reader_clt, writer_clt),
1637
+ timeout=config.CLIENT_HANDSHAKE_TIMEOUT)
1638
+ except asyncio.TimeoutError:
1639
+ update_stats(handshake_timeouts=1)
1640
+ return
1641
+
1642
+ if not clt_data:
1643
+ return
1644
+
1645
+ reader_clt, writer_clt, proto_tag, user, dc_idx, enc_key_and_iv, peer = clt_data
1646
+ cl_ip, cl_port = peer
1647
+
1648
+ update_user_stats(user, connects=1)
1649
+
1650
+ connect_directly = (not config.USE_MIDDLE_PROXY or disable_middle_proxy)
1651
+
1652
+ if connect_directly:
1653
+ if config.FAST_MODE:
1654
+ tg_data = await do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=enc_key_and_iv)
1655
+ else:
1656
+ tg_data = await do_direct_handshake(proto_tag, dc_idx)
1657
+ else:
1658
+ tg_data = await do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port)
1659
+
1660
+ if not tg_data:
1661
+ return
1662
+
1663
+ reader_tg, writer_tg = tg_data
1664
+
1665
+ if connect_directly and config.FAST_MODE:
1666
+ class FakeEncryptor:
1667
+ def encrypt(self, data):
1668
+ return data
1669
+
1670
+ class FakeDecryptor:
1671
+ def decrypt(self, data):
1672
+ return data
1673
+
1674
+ reader_tg.decryptor = FakeDecryptor()
1675
+ writer_clt.encryptor = FakeEncryptor()
1676
+
1677
+ if not connect_directly:
1678
+ if proto_tag == PROTO_TAG_ABRIDGED:
1679
+ reader_clt = MTProtoCompactFrameStreamReader(reader_clt)
1680
+ writer_clt = MTProtoCompactFrameStreamWriter(writer_clt)
1681
+ elif proto_tag == PROTO_TAG_INTERMEDIATE:
1682
+ reader_clt = MTProtoIntermediateFrameStreamReader(reader_clt)
1683
+ writer_clt = MTProtoIntermediateFrameStreamWriter(writer_clt)
1684
+ elif proto_tag == PROTO_TAG_SECURE:
1685
+ reader_clt = MTProtoSecureIntermediateFrameStreamReader(reader_clt)
1686
+ writer_clt = MTProtoSecureIntermediateFrameStreamWriter(writer_clt)
1687
+ else:
1688
+ return
1689
+
1690
+ tg_to_clt = tg_connect_reader_to_writer(reader_tg, writer_clt, user,
1691
+ get_to_clt_bufsize(), False)
1692
+ clt_to_tg = tg_connect_reader_to_writer(reader_clt, writer_tg,
1693
+ user, get_to_tg_bufsize(), True)
1694
+ task_tg_to_clt = asyncio.ensure_future(tg_to_clt)
1695
+ task_clt_to_tg = asyncio.ensure_future(clt_to_tg)
1696
+
1697
+ update_user_stats(user, curr_connects=1)
1698
+
1699
+ tcp_limit_hit = (
1700
+ user in config.USER_MAX_TCP_CONNS and
1701
+ user_stats[user]["curr_connects"] > config.USER_MAX_TCP_CONNS[user]
1702
+ )
1703
+
1704
+ user_expired = (
1705
+ user in config.USER_EXPIRATIONS and
1706
+ datetime.datetime.now() > config.USER_EXPIRATIONS[user]
1707
+ )
1708
+
1709
+ user_data_quota_hit = (
1710
+ user in config.USER_DATA_QUOTA and
1711
+ (user_stats[user]["octets_to_client"] +
1712
+ user_stats[user]["octets_from_client"] > config.USER_DATA_QUOTA[user])
1713
+ )
1714
+
1715
+ if (not tcp_limit_hit) and (not user_expired) and (not user_data_quota_hit):
1716
+ start = time.time()
1717
+ await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED)
1718
+ update_durations(time.time() - start)
1719
+
1720
+ update_user_stats(user, curr_connects=-1)
1721
+
1722
+ task_tg_to_clt.cancel()
1723
+ task_clt_to_tg.cancel()
1724
+
1725
+ writer_tg.transport.abort()
1726
+
1727
+
1728
+ async def handle_client_wrapper(reader, writer):
1729
+ try:
1730
+ await handle_client(reader, writer)
1731
+ except (asyncio.IncompleteReadError, asyncio.CancelledError):
1732
+ pass
1733
+ except (ConnectionResetError, TimeoutError, BrokenPipeError):
1734
+ pass
1735
+ except Exception:
1736
+ traceback.print_exc()
1737
+ finally:
1738
+ writer.transport.abort()
1739
+
1740
+
1741
+ def make_metrics_pkt(metrics):
1742
+ pkt_body_list = []
1743
+ used_names = set()
1744
+
1745
+ for name, m_type, desc, val in metrics:
1746
+ name = config.METRICS_PREFIX + name
1747
+ if name not in used_names:
1748
+ pkt_body_list.append("# HELP %s %s" % (name, desc))
1749
+ pkt_body_list.append("# TYPE %s %s" % (name, m_type))
1750
+ used_names.add(name)
1751
+
1752
+ if isinstance(val, dict):
1753
+ tags = []
1754
+ for tag, tag_val in val.items():
1755
+ if tag == "val":
1756
+ continue
1757
+ tag_val = tag_val.replace('"', r'\"')
1758
+ tags.append('%s="%s"' % (tag, tag_val))
1759
+ pkt_body_list.append("%s{%s} %s" % (name, ",".join(tags), val["val"]))
1760
+ else:
1761
+ pkt_body_list.append("%s %s" % (name, val))
1762
+ pkt_body = "\n".join(pkt_body_list) + "\n"
1763
+
1764
+ pkt_header_list = []
1765
+ pkt_header_list.append("HTTP/1.1 200 OK")
1766
+ pkt_header_list.append("Connection: close")
1767
+ pkt_header_list.append("Content-Length: %d" % len(pkt_body))
1768
+ pkt_header_list.append("Content-Type: text/plain; version=0.0.4; charset=utf-8")
1769
+ pkt_header_list.append("Date: %s" % time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()))
1770
+
1771
+ pkt_header = "\r\n".join(pkt_header_list)
1772
+
1773
+ pkt = pkt_header + "\r\n\r\n" + pkt_body
1774
+ return pkt
1775
+
1776
+
1777
+ async def handle_metrics(reader, writer):
1778
+ global stats
1779
+ global user_stats
1780
+ global my_ip_info
1781
+ global proxy_start_time
1782
+ global proxy_links
1783
+ global last_clients_with_time_skew
1784
+ global last_clients_with_same_handshake
1785
+
1786
+ client_ip = writer.get_extra_info("peername")[0]
1787
+ if client_ip not in config.METRICS_WHITELIST:
1788
+ writer.close()
1789
+ return
1790
+
1791
+ try:
1792
+ metrics = []
1793
+ metrics.append(["uptime", "counter", "proxy uptime", time.time() - proxy_start_time])
1794
+ metrics.append(["connects_bad", "counter", "connects with bad secret",
1795
+ stats["connects_bad"]])
1796
+ metrics.append(["connects_all", "counter", "incoming connects", stats["connects_all"]])
1797
+ metrics.append(["handshake_timeouts", "counter", "number of timed out handshakes",
1798
+ stats["handshake_timeouts"]])
1799
+
1800
+ if config.METRICS_EXPORT_LINKS:
1801
+ for link in proxy_links:
1802
+ link_as_metric = link.copy()
1803
+ link_as_metric["val"] = 1
1804
+ metrics.append(["proxy_link_info", "counter",
1805
+ "the proxy link info", link_as_metric])
1806
+
1807
+ bucket_start = 0
1808
+ for bucket in STAT_DURATION_BUCKETS:
1809
+ bucket_end = bucket if bucket != STAT_DURATION_BUCKETS[-1] else "+Inf"
1810
+ metric = {
1811
+ "bucket": "%s-%s" % (bucket_start, bucket_end),
1812
+ "val": stats["connects_with_duration_le_%s" % str(bucket)]
1813
+ }
1814
+ metrics.append(["connects_by_duration", "counter", "connects by duration", metric])
1815
+ bucket_start = bucket_end
1816
+
1817
+ user_metrics_desc = [
1818
+ ["user_connects", "counter", "user connects", "connects"],
1819
+ ["user_connects_curr", "gauge", "current user connects", "curr_connects"],
1820
+ ["user_octets", "counter", "octets proxied for user",
1821
+ "octets_from_client+octets_to_client"],
1822
+ ["user_msgs", "counter", "msgs proxied for user",
1823
+ "msgs_from_client+msgs_to_client"],
1824
+ ["user_octets_from", "counter", "octets proxied from user", "octets_from_client"],
1825
+ ["user_octets_to", "counter", "octets proxied to user", "octets_to_client"],
1826
+ ["user_msgs_from", "counter", "msgs proxied from user", "msgs_from_client"],
1827
+ ["user_msgs_to", "counter", "msgs proxied to user", "msgs_to_client"],
1828
+ ]
1829
+
1830
+ for m_name, m_type, m_desc, stat_key in user_metrics_desc:
1831
+ for user, stat in user_stats.items():
1832
+ if "+" in stat_key:
1833
+ val = 0
1834
+ for key_part in stat_key.split("+"):
1835
+ val += stat[key_part]
1836
+ else:
1837
+ val = stat[stat_key]
1838
+ metric = {"user": user, "val": val}
1839
+ metrics.append([m_name, m_type, m_desc, metric])
1840
+
1841
+ pkt = make_metrics_pkt(metrics)
1842
+ writer.write(pkt.encode())
1843
+ await writer.drain()
1844
+
1845
+ except Exception:
1846
+ traceback.print_exc()
1847
+ finally:
1848
+ writer.close()
1849
+
1850
+
1851
+ async def stats_printer():
1852
+ global user_stats
1853
+ global last_client_ips
1854
+ global last_clients_with_time_skew
1855
+ global last_clients_with_same_handshake
1856
+
1857
+ while True:
1858
+ await asyncio.sleep(config.STATS_PRINT_PERIOD)
1859
+
1860
+ print("Stats for", time.strftime("%d.%m.%Y %H:%M:%S"))
1861
+ for user, stat in user_stats.items():
1862
+ print("%s: %d connects (%d current), %.2f MB, %d msgs" % (
1863
+ user, stat["connects"], stat["curr_connects"],
1864
+ (stat["octets_from_client"] + stat["octets_to_client"]) / 1000000,
1865
+ stat["msgs_from_client"] + stat["msgs_to_client"]))
1866
+ print(flush=True)
1867
+
1868
+ if last_client_ips:
1869
+ print("New IPs:")
1870
+ for ip in last_client_ips:
1871
+ print(ip)
1872
+ print(flush=True)
1873
+ last_client_ips.clear()
1874
+
1875
+ if last_clients_with_time_skew:
1876
+ print("Clients with time skew (possible replay-attackers):")
1877
+ for ip, skew_minutes in last_clients_with_time_skew.items():
1878
+ print("%s, clocks were %d minutes behind" % (ip, skew_minutes))
1879
+ print(flush=True)
1880
+ last_clients_with_time_skew.clear()
1881
+ if last_clients_with_same_handshake:
1882
+ print("Clients with duplicate handshake (likely replay-attackers):")
1883
+ for ip, times in last_clients_with_same_handshake.items():
1884
+ print("%s, %d times" % (ip, times))
1885
+ print(flush=True)
1886
+ last_clients_with_same_handshake.clear()
1887
+
1888
+
1889
+ async def make_https_req(url, host="core.telegram.org"):
1890
+ """ Make request, return resp body and headers. """
1891
+ SSL_PORT = 443
1892
+ url_data = urllib.parse.urlparse(url)
1893
+
1894
+ HTTP_REQ_TEMPLATE = "\r\n".join(["GET %s HTTP/1.1", "Host: %s",
1895
+ "Connection: close"]) + "\r\n\r\n"
1896
+ reader, writer = await asyncio.open_connection(url_data.netloc, SSL_PORT, ssl=True)
1897
+ req = HTTP_REQ_TEMPLATE % (urllib.parse.quote(url_data.path), host)
1898
+ writer.write(req.encode("utf8"))
1899
+ data = await reader.read()
1900
+ writer.close()
1901
+
1902
+ headers, body = data.split(b"\r\n\r\n", 1)
1903
+ return headers, body
1904
+
1905
+
1906
+ def gen_tls_client_hello_msg(server_name):
1907
+ msg = bytearray()
1908
+ msg += b"\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03" + myrandom.getrandbytes(32)
1909
+ msg += b"\x20" + myrandom.getrandbytes(32)
1910
+ msg += b"\x00\x22\x4a\x4a\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9"
1911
+ msg += b"\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x0a\x01\x00\x01\x91"
1912
+ msg += b"\xda\xda\x00\x00\x00\x00"
1913
+ msg += int.to_bytes(len(server_name) + 5, 2, "big")
1914
+ msg += int.to_bytes(len(server_name) + 3, 2, "big") + b"\x00"
1915
+ msg += int.to_bytes(len(server_name), 2, "big") + server_name.encode("ascii")
1916
+ msg += b"\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0a\x00\x08\xaa\xaa\x00\x1d\x00"
1917
+ msg += b"\x17\x00\x18\x00\x0b\x00\x02\x01\x00\x00\x23\x00\x00\x00\x10\x00\x0e\x00\x0c\x02"
1918
+ msg += b"\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00"
1919
+ msg += b"\x00\x0d\x00\x14\x00\x12\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06"
1920
+ msg += b"\x06\x01\x02\x01\x00\x12\x00\x00\x00\x33\x00\x2b\x00\x29\xaa\xaa\x00\x01\x00\x00"
1921
+ msg += b"\x1d\x00\x20" + gen_x25519_public_key()
1922
+ msg += b"\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a\xba\xba\x03\x04\x03\x03\x03\x02\x03"
1923
+ msg += b"\x01\x00\x1b\x00\x03\x02\x00\x02\x3a\x3a\x00\x01\x00\x00\x15"
1924
+ msg += int.to_bytes(517 - len(msg) - 2, 2, "big")
1925
+ msg += b"\x00" * (517 - len(msg))
1926
+ return bytes(msg)
1927
+
1928
+
1929
+ async def get_encrypted_cert(host, port, server_name):
1930
+ async def get_tls_record(reader):
1931
+ try:
1932
+ record_type = (await reader.readexactly(1))[0]
1933
+ tls_version = await reader.readexactly(2)
1934
+ if tls_version != b"\x03\x03":
1935
+ return 0, b""
1936
+ record_len = int.from_bytes(await reader.readexactly(2), "big")
1937
+ record = await reader.readexactly(record_len)
1938
+
1939
+ return record_type, record
1940
+ except asyncio.IncompleteReadError:
1941
+ return 0, b""
1942
+
1943
+ reader, writer = await asyncio.open_connection(host, port)
1944
+ writer.write(gen_tls_client_hello_msg(server_name))
1945
+ await writer.drain()
1946
+
1947
+ record1_type, record1 = await get_tls_record(reader)
1948
+ if record1_type != 22:
1949
+ return b""
1950
+
1951
+ record2_type, record2 = await get_tls_record(reader)
1952
+ if record2_type != 20:
1953
+ return b""
1954
+
1955
+ record3_type, record3 = await get_tls_record(reader)
1956
+ if record3_type != 23:
1957
+ return b""
1958
+
1959
+ if len(record3) < MIN_CERT_LEN:
1960
+ record4_type, record4 = await get_tls_record(reader)
1961
+ if record4_type != 23:
1962
+ return b""
1963
+ msg = ("The MASK_HOST %s sent some TLS record before certificate record, this makes the " +
1964
+ "proxy more detectable") % config.MASK_HOST
1965
+ print_err(msg)
1966
+
1967
+ return record4
1968
+
1969
+ return record3
1970
+
1971
+
1972
+ async def get_mask_host_cert_len():
1973
+ global fake_cert_len
1974
+
1975
+ GET_CERT_TIMEOUT = 10
1976
+ MASK_ENABLING_CHECK_PERIOD = 60
1977
+
1978
+ while True:
1979
+ try:
1980
+ if not config.MASK:
1981
+ # do nothing
1982
+ await asyncio.sleep(MASK_ENABLING_CHECK_PERIOD)
1983
+ continue
1984
+
1985
+ task = get_encrypted_cert(config.MASK_HOST, config.MASK_PORT, config.TLS_DOMAIN)
1986
+ cert = await asyncio.wait_for(task, timeout=GET_CERT_TIMEOUT)
1987
+ if cert:
1988
+ if len(cert) < MIN_CERT_LEN:
1989
+ msg = ("The MASK_HOST %s returned several TLS records, this is not supported" %
1990
+ config.MASK_HOST)
1991
+ print_err(msg)
1992
+ elif len(cert) != fake_cert_len:
1993
+ fake_cert_len = len(cert)
1994
+ print_err("Got cert from the MASK_HOST %s, its length is %d" %
1995
+ (config.MASK_HOST, fake_cert_len))
1996
+ else:
1997
+ print_err("The MASK_HOST %s is not TLS 1.3 host, this is not recommended" %
1998
+ config.MASK_HOST)
1999
+ except ConnectionRefusedError:
2000
+ print_err("The MASK_HOST %s is refusing connections, this is not recommended" %
2001
+ config.MASK_HOST)
2002
+ except (TimeoutError, asyncio.TimeoutError):
2003
+ print_err("Got timeout while getting TLS handshake from MASK_HOST %s" %
2004
+ config.MASK_HOST)
2005
+ except Exception as E:
2006
+ print_err("Failed to connect to MASK_HOST %s: %s" % (
2007
+ config.MASK_HOST, E))
2008
+
2009
+ await asyncio.sleep(config.GET_CERT_LEN_PERIOD)
2010
+
2011
+
2012
+ async def get_srv_time():
2013
+ TIME_SYNC_ADDR = "https://core.telegram.org/getProxySecret"
2014
+ MAX_TIME_SKEW = 30
2015
+
2016
+ global disable_middle_proxy
2017
+ global is_time_skewed
2018
+
2019
+ want_to_reenable_advertising = False
2020
+ while True:
2021
+ try:
2022
+ headers, secret = await make_https_req(TIME_SYNC_ADDR)
2023
+
2024
+ for line in headers.split(b"\r\n"):
2025
+ if not line.startswith(b"Date: "):
2026
+ continue
2027
+ line = line[len("Date: "):].decode()
2028
+ srv_time = datetime.datetime.strptime(line, "%a, %d %b %Y %H:%M:%S %Z")
2029
+ srv_time = srv_time.replace(tzinfo=datetime.timezone.utc)
2030
+ now_time = datetime.datetime.now(datetime.timezone.utc)
2031
+ is_time_skewed = (now_time-srv_time).total_seconds() > MAX_TIME_SKEW
2032
+ if is_time_skewed and config.USE_MIDDLE_PROXY and not disable_middle_proxy:
2033
+ print_err("Time skew detected, please set the clock")
2034
+ print_err("Server time:", srv_time, "your time:", now_time)
2035
+ print_err("Disabling advertising to continue serving")
2036
+ print_err("Putting down the shields against replay attacks")
2037
+
2038
+ disable_middle_proxy = True
2039
+ want_to_reenable_advertising = True
2040
+ elif not is_time_skewed and want_to_reenable_advertising:
2041
+ print_err("Time is ok, reenabling advertising")
2042
+ disable_middle_proxy = False
2043
+ want_to_reenable_advertising = False
2044
+ except Exception as E:
2045
+ print_err("Error getting server time", E)
2046
+
2047
+ await asyncio.sleep(config.GET_TIME_PERIOD)
2048
+
2049
+
2050
+ async def clear_ip_resolving_cache():
2051
+ global mask_host_cached_ip
2052
+ min_sleep = myrandom.randrange(60 - 10, 60 + 10)
2053
+ max_sleep = myrandom.randrange(120 - 10, 120 + 10)
2054
+ while True:
2055
+ mask_host_cached_ip = None
2056
+ await asyncio.sleep(myrandom.randrange(min_sleep, max_sleep))
2057
+
2058
+
2059
+ async def update_middle_proxy_info():
2060
+ async def get_new_proxies(url):
2061
+ PROXY_REGEXP = re.compile(r"proxy_for\s+(-?\d+)\s+(.+):(\d+)\s*;")
2062
+ ans = {}
2063
+ headers, body = await make_https_req(url)
2064
+
2065
+ fields = PROXY_REGEXP.findall(body.decode("utf8"))
2066
+ if fields:
2067
+ for dc_idx, host, port in fields:
2068
+ if host.startswith("[") and host.endswith("]"):
2069
+ host = host[1:-1]
2070
+ dc_idx, port = int(dc_idx), int(port)
2071
+ if dc_idx not in ans:
2072
+ ans[dc_idx] = [(host, port)]
2073
+ else:
2074
+ ans[dc_idx].append((host, port))
2075
+ return ans
2076
+
2077
+ PROXY_INFO_ADDR = "https://core.telegram.org/getProxyConfig"
2078
+ PROXY_INFO_ADDR_V6 = "https://core.telegram.org/getProxyConfigV6"
2079
+ PROXY_SECRET_ADDR = "https://core.telegram.org/getProxySecret"
2080
+
2081
+ global TG_MIDDLE_PROXIES_V4
2082
+ global TG_MIDDLE_PROXIES_V6
2083
+ global PROXY_SECRET
2084
+
2085
+ while True:
2086
+ try:
2087
+ v4_proxies = await get_new_proxies(PROXY_INFO_ADDR)
2088
+ if not v4_proxies:
2089
+ raise Exception("no proxy data")
2090
+ TG_MIDDLE_PROXIES_V4 = v4_proxies
2091
+ except Exception as E:
2092
+ print_err("Error updating middle proxy list:", E)
2093
+
2094
+ try:
2095
+ v6_proxies = await get_new_proxies(PROXY_INFO_ADDR_V6)
2096
+ if not v6_proxies:
2097
+ raise Exception("no proxy data (ipv6)")
2098
+ TG_MIDDLE_PROXIES_V6 = v6_proxies
2099
+ except Exception as E:
2100
+ print_err("Error updating middle proxy list for IPv6:", E)
2101
+
2102
+ try:
2103
+ headers, secret = await make_https_req(PROXY_SECRET_ADDR)
2104
+ if not secret:
2105
+ raise Exception("no secret")
2106
+ if secret != PROXY_SECRET:
2107
+ PROXY_SECRET = secret
2108
+ print_err("Middle proxy secret updated")
2109
+ except Exception as E:
2110
+ print_err("Error updating middle proxy secret, using old", E)
2111
+
2112
+ await asyncio.sleep(config.PROXY_INFO_UPDATE_PERIOD)
2113
+
2114
+
2115
+ def init_ip_info():
2116
+ global my_ip_info
2117
+ global disable_middle_proxy
2118
+
2119
+ def get_ip_from_url(url):
2120
+ TIMEOUT = 5
2121
+ try:
2122
+ with urllib.request.urlopen(url, timeout=TIMEOUT) as f:
2123
+ if f.status != 200:
2124
+ raise Exception("Invalid status code")
2125
+ return f.read().decode().strip()
2126
+ except Exception:
2127
+ return None
2128
+
2129
+ IPV4_URL1 = "http://v4.ident.me/"
2130
+ IPV4_URL2 = "http://ipv4.icanhazip.com/"
2131
+
2132
+ IPV6_URL1 = "http://v6.ident.me/"
2133
+ IPV6_URL2 = "http://ipv6.icanhazip.com/"
2134
+
2135
+ my_ip_info["ipv4"] = get_ip_from_url(IPV4_URL1) or get_ip_from_url(IPV4_URL2)
2136
+ my_ip_info["ipv6"] = get_ip_from_url(IPV6_URL1) or get_ip_from_url(IPV6_URL2)
2137
+
2138
+ # the server can return ipv4 address instead of ipv6
2139
+ if my_ip_info["ipv6"] and ":" not in my_ip_info["ipv6"]:
2140
+ my_ip_info["ipv6"] = None
2141
+
2142
+ if my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"]):
2143
+ print_err("IPv6 found, using it for external communication")
2144
+
2145
+ if config.USE_MIDDLE_PROXY:
2146
+ if not my_ip_info["ipv4"] and not my_ip_info["ipv6"]:
2147
+ print_err("Failed to determine your ip, advertising disabled")
2148
+ disable_middle_proxy = True
2149
+
2150
+
2151
+ def print_tg_info():
2152
+ global my_ip_info
2153
+ global proxy_links
2154
+
2155
+ print_default_warning = False
2156
+
2157
+ if config.PORT == 3256:
2158
+ print("The default port 3256 is used, this is not recommended", flush=True)
2159
+ if not config.MODES["classic"] and not config.MODES["secure"]:
2160
+ print("Since you have TLS only mode enabled the best port is 443", flush=True)
2161
+ print_default_warning = True
2162
+
2163
+ if not config.MY_DOMAIN:
2164
+ ip_addrs = [ip for ip in my_ip_info.values() if ip]
2165
+ if not ip_addrs:
2166
+ ip_addrs = ["YOUR_IP"]
2167
+ else:
2168
+ ip_addrs = [config.MY_DOMAIN]
2169
+
2170
+ proxy_links = []
2171
+
2172
+ for user, secret in sorted(config.USERS.items(), key=lambda x: x[0]):
2173
+ for ip in ip_addrs:
2174
+ if config.MODES["classic"]:
2175
+ params = {"server": ip, "port": config.PORT, "secret": secret}
2176
+ params_encoded = urllib.parse.urlencode(params, safe=':')
2177
+ classic_link = "tg://proxy?{}".format(params_encoded)
2178
+ proxy_links.append({"user": user, "link": classic_link})
2179
+ print("{}: {}".format(user, classic_link), flush=True)
2180
+
2181
+ if config.MODES["secure"]:
2182
+ params = {"server": ip, "port": config.PORT, "secret": "dd" + secret}
2183
+ params_encoded = urllib.parse.urlencode(params, safe=':')
2184
+ dd_link = "tg://proxy?{}".format(params_encoded)
2185
+ proxy_links.append({"user": user, "link": dd_link})
2186
+ print("{}: {}".format(user, dd_link), flush=True)
2187
+
2188
+ if config.MODES["tls"]:
2189
+ tls_secret = "ee" + secret + config.TLS_DOMAIN.encode().hex()
2190
+ # the base64 links is buggy on ios
2191
+ # tls_secret = bytes.fromhex("ee" + secret) + config.TLS_DOMAIN.encode()
2192
+ # tls_secret_base64 = base64.urlsafe_b64encode(tls_secret)
2193
+ params = {"server": ip, "port": config.PORT, "secret": tls_secret}
2194
+ params_encoded = urllib.parse.urlencode(params, safe=':')
2195
+ tls_link = "tg://proxy?{}".format(params_encoded)
2196
+ proxy_links.append({"user": user, "link": tls_link})
2197
+ print("{}: {}".format(user, tls_link), flush=True)
2198
+
2199
+ if secret in ["00000000000000000000000000000000", "0123456789abcdef0123456789abcdef",
2200
+ "00000000000000000000000000000001"]:
2201
+ msg = "The default secret {} is used, this is not recommended".format(secret)
2202
+ print(msg, flush=True)
2203
+ random_secret = "".join(myrandom.choice("0123456789abcdef") for i in range(32))
2204
+ print("You can change it to this random secret:", random_secret, flush=True)
2205
+ print_default_warning = True
2206
+
2207
+ if config.TLS_DOMAIN == "www.google.com":
2208
+ print("The default TLS_DOMAIN www.google.com is used, this is not recommended", flush=True)
2209
+ msg = "You should use random existing domain instead, bad clients are proxied there"
2210
+ print(msg, flush=True)
2211
+ print_default_warning = True
2212
+
2213
+ if print_default_warning:
2214
+ print_err("Warning: one or more default settings detected")
2215
+
2216
+
2217
+ def setup_files_limit():
2218
+ try:
2219
+ import resource
2220
+ soft_fd_limit, hard_fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
2221
+ resource.setrlimit(resource.RLIMIT_NOFILE, (hard_fd_limit, hard_fd_limit))
2222
+ except (ValueError, OSError):
2223
+ print("Failed to increase the limit of opened files", flush=True, file=sys.stderr)
2224
+ except ImportError:
2225
+ pass
2226
+
2227
+
2228
+ def setup_asyncio():
2229
+ # get rid of annoying "socket.send() raised exception" log messages
2230
+ asyncio.constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES = 100
2231
+
2232
+
2233
+ def setup_signals():
2234
+ if hasattr(signal, 'SIGUSR1'):
2235
+ def debug_signal(signum, frame):
2236
+ import pdb
2237
+ pdb.set_trace()
2238
+
2239
+ signal.signal(signal.SIGUSR1, debug_signal)
2240
+
2241
+ if hasattr(signal, 'SIGUSR2'):
2242
+ def reload_signal(signum, frame):
2243
+ init_config()
2244
+ ensure_users_in_user_stats()
2245
+ apply_upstream_proxy_settings()
2246
+ print("Config reloaded", flush=True, file=sys.stderr)
2247
+ print_tg_info()
2248
+
2249
+ signal.signal(signal.SIGUSR2, reload_signal)
2250
+
2251
+
2252
+ def try_setup_uvloop():
2253
+ if config.SOCKS5_HOST and config.SOCKS5_PORT:
2254
+ # socks mode is not compatible with uvloop
2255
+ return
2256
+ try:
2257
+ import uvloop
2258
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
2259
+ print_err("Found uvloop, using it for optimal performance")
2260
+ except ImportError:
2261
+ pass
2262
+
2263
+
2264
+ def remove_unix_socket(path):
2265
+ try:
2266
+ if stat.S_ISSOCK(os.stat(path).st_mode):
2267
+ os.unlink(path)
2268
+ except (FileNotFoundError, NotADirectoryError):
2269
+ pass
2270
+
2271
+
2272
+ def loop_exception_handler(loop, context):
2273
+ exception = context.get("exception")
2274
+ transport = context.get("transport")
2275
+ if exception:
2276
+ if isinstance(exception, TimeoutError):
2277
+ if transport:
2278
+ transport.abort()
2279
+ return
2280
+ if isinstance(exception, OSError):
2281
+ IGNORE_ERRNO = {
2282
+ 10038, # operation on non-socket on Windows, likely because fd == -1
2283
+ 121, # the semaphore timeout period has expired on Windows
2284
+ }
2285
+
2286
+ FORCE_CLOSE_ERRNO = {
2287
+ 113, # no route to host
2288
+
2289
+ }
2290
+ if exception.errno in IGNORE_ERRNO:
2291
+ return
2292
+ elif exception.errno in FORCE_CLOSE_ERRNO:
2293
+ if transport:
2294
+ transport.abort()
2295
+ return
2296
+
2297
+ loop.default_exception_handler(context)
2298
+
2299
+
2300
+ def create_servers(loop):
2301
+ servers = []
2302
+
2303
+ reuse_port = hasattr(socket, "SO_REUSEPORT")
2304
+ has_unix = hasattr(socket, "AF_UNIX")
2305
+
2306
+ if config.LISTEN_ADDR_IPV4:
2307
+ task = asyncio.start_server(handle_client_wrapper, config.LISTEN_ADDR_IPV4, config.PORT,
2308
+ limit=get_to_tg_bufsize(), reuse_port=reuse_port)
2309
+ servers.append(loop.run_until_complete(task))
2310
+
2311
+ if config.LISTEN_ADDR_IPV6 and socket.has_ipv6:
2312
+ task = asyncio.start_server(handle_client_wrapper, config.LISTEN_ADDR_IPV6, config.PORT,
2313
+ limit=get_to_tg_bufsize(), reuse_port=reuse_port)
2314
+ servers.append(loop.run_until_complete(task))
2315
+
2316
+ if config.LISTEN_UNIX_SOCK and has_unix:
2317
+ remove_unix_socket(config.LISTEN_UNIX_SOCK)
2318
+ task = asyncio.start_unix_server(handle_client_wrapper, config.LISTEN_UNIX_SOCK,
2319
+ limit=get_to_tg_bufsize())
2320
+ servers.append(loop.run_until_complete(task))
2321
+ os.chmod(config.LISTEN_UNIX_SOCK, 0o666)
2322
+
2323
+ if config.METRICS_PORT is not None:
2324
+ if config.METRICS_LISTEN_ADDR_IPV4:
2325
+ task = asyncio.start_server(handle_metrics, config.METRICS_LISTEN_ADDR_IPV4,
2326
+ config.METRICS_PORT)
2327
+ servers.append(loop.run_until_complete(task))
2328
+ if config.METRICS_LISTEN_ADDR_IPV6 and socket.has_ipv6:
2329
+ task = asyncio.start_server(handle_metrics, config.METRICS_LISTEN_ADDR_IPV6,
2330
+ config.METRICS_PORT)
2331
+ servers.append(loop.run_until_complete(task))
2332
+
2333
+ return servers
2334
+
2335
+
2336
+ def create_utilitary_tasks(loop):
2337
+ tasks = []
2338
+
2339
+ stats_printer_task = asyncio.Task(stats_printer(), loop=loop)
2340
+ tasks.append(stats_printer_task)
2341
+
2342
+ if config.USE_MIDDLE_PROXY:
2343
+ middle_proxy_updater_task = asyncio.Task(update_middle_proxy_info(), loop=loop)
2344
+ tasks.append(middle_proxy_updater_task)
2345
+
2346
+ if config.GET_TIME_PERIOD:
2347
+ time_get_task = asyncio.Task(get_srv_time(), loop=loop)
2348
+ tasks.append(time_get_task)
2349
+
2350
+ get_cert_len_task = asyncio.Task(get_mask_host_cert_len(), loop=loop)
2351
+ tasks.append(get_cert_len_task)
2352
+
2353
+ clear_resolving_cache_task = asyncio.Task(clear_ip_resolving_cache(), loop=loop)
2354
+ tasks.append(clear_resolving_cache_task)
2355
+
2356
+ return tasks
2357
+
2358
+
2359
+ def main():
2360
+ init_config()
2361
+ ensure_users_in_user_stats()
2362
+ apply_upstream_proxy_settings()
2363
+ init_ip_info()
2364
+ print_tg_info()
2365
+
2366
+ setup_asyncio()
2367
+ setup_files_limit()
2368
+ setup_signals()
2369
+ try_setup_uvloop()
2370
+
2371
+ init_proxy_start_time()
2372
+
2373
+ if sys.platform == "win32":
2374
+ loop = asyncio.ProactorEventLoop()
2375
+ else:
2376
+ loop = asyncio.new_event_loop()
2377
+
2378
+ asyncio.set_event_loop(loop)
2379
+ loop.set_exception_handler(loop_exception_handler)
2380
+
2381
+ utilitary_tasks = create_utilitary_tasks(loop)
2382
+ for task in utilitary_tasks:
2383
+ asyncio.ensure_future(task)
2384
+
2385
+ servers = create_servers(loop)
2386
+
2387
+ try:
2388
+ loop.run_forever()
2389
+ except KeyboardInterrupt:
2390
+ pass
2391
+
2392
+ if hasattr(asyncio, "all_tasks"):
2393
+ tasks = asyncio.all_tasks(loop)
2394
+ else:
2395
+ # for compatibility with Python 3.6
2396
+ tasks = asyncio.Task.all_tasks(loop)
2397
+
2398
+ for task in tasks:
2399
+ task.cancel()
2400
+
2401
+ for server in servers:
2402
+ server.close()
2403
+ loop.run_until_complete(server.wait_closed())
2404
+
2405
+ has_unix = hasattr(socket, "AF_UNIX")
2406
+
2407
+ if config.LISTEN_UNIX_SOCK and has_unix:
2408
+ remove_unix_socket(config.LISTEN_UNIX_SOCK)
2409
+
2410
+ loop.close()
2411
+
2412
+
2413
+ if __name__ == "__main__":
2414
+ main()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ cryptography==42.0.5
2
+ uvloop==0.19.0
3
+ flask==3.0.3
4
+ psutil==5.9.8
runner.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import sys
3
+ import os
4
+
5
+ # Set uvloop policy if available
6
+ try:
7
+ import uvloop
8
+ import asyncio
9
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
10
+ print("Using uvloop for maximum performance.", flush=True)
11
+ except ImportError:
12
+ print("uvloop not installed, using default asyncio loop.", flush=True)
13
+
14
+ import mtprotoproxy
15
+ from webui import start_webui
16
+
17
+ def main():
18
+ # Start the Flask WebUI in a background daemon thread
19
+ web_thread = threading.Thread(target=start_webui, daemon=True)
20
+ web_thread.start()
21
+ print("Started WebUI on port 7860.", flush=True)
22
+
23
+ # We need to simulate arguments to mtprotoproxy if it expects sys.argv[1] as config
24
+ if len(sys.argv) < 2:
25
+ # Defaulting sys.argv to have config.py as first argument so mtprotoproxy picks it up
26
+ sys.argv.append("config.py")
27
+
28
+ print("Starting MTProto Proxy...", flush=True)
29
+ # Start mtprotoproxy main loop
30
+ # mtprotoproxy.main() starts the loop and blocks forever
31
+ try:
32
+ mtprotoproxy.main()
33
+ except KeyboardInterrupt:
34
+ print("Interrupted by user. Exiting...", flush=True)
35
+
36
+ if __name__ == "__main__":
37
+ main()
test_docker.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ docker run --rm python:3.12-slim bash -c "apt-get update && apt-get install -y gcc python3-dev && pip install uvloop cryptography flask psutil gunicorn"
webui.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, jsonify, render_template_string
2
+ import psutil
3
+ import mtprotoproxy
4
+ import time
5
+ import os
6
+
7
+ app = Flask(__name__)
8
+
9
+ HTML_TEMPLATE = """
10
+ <!DOCTYPE html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="UTF-8">
14
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
15
+ <title>MTProto Proxy Dashboard</title>
16
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
17
+ <style>
18
+ body { background-color: #f8f9fa; padding-top: 20px; }
19
+ .card { margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
20
+ .metric-value { font-size: 2rem; font-weight: bold; color: #0d6efd; }
21
+ </style>
22
+ <script>
23
+ async function updateStats() {
24
+ try {
25
+ const response = await fetch('/api/stats');
26
+ const data = await response.json();
27
+
28
+ document.getElementById('cpu-load').innerText = data.cpu_percent + '%';
29
+ document.getElementById('mem-load').innerText = data.mem_percent + '%';
30
+
31
+ // Calculate total connections
32
+ let totalConnections = 0;
33
+ let userHtml = '';
34
+ for (const [user, stats] of Object.entries(data.user_stats)) {
35
+ totalConnections += stats.curr_connects || 0;
36
+ userHtml += `<tr>
37
+ <td>${user}</td>
38
+ <td>${stats.curr_connects || 0}</td>
39
+ <td>${stats.connects || 0}</td>
40
+ <td>${(((stats.octets_from_client || 0) + (stats.octets_to_client || 0)) / 1024 / 1024).toFixed(2)} MB</td>
41
+ </tr>`;
42
+ }
43
+ document.getElementById('total-users').innerText = totalConnections;
44
+ document.getElementById('user-table-body').innerHTML = userHtml;
45
+ } catch (e) {
46
+ console.error("Failed to fetch stats", e);
47
+ }
48
+ }
49
+
50
+ setInterval(updateStats, 2000);
51
+ window.onload = updateStats;
52
+ </script>
53
+ </head>
54
+ <body>
55
+ <div class="container">
56
+ <h1 class="mb-4 text-center">MTProto Proxy Dashboard</h1>
57
+
58
+ <div class="row">
59
+ <div class="col-md-4">
60
+ <div class="card text-center">
61
+ <div class="card-body">
62
+ <h5 class="card-title">Active Connections</h5>
63
+ <p class="metric-value" id="total-users">0</p>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="col-md-4">
68
+ <div class="card text-center">
69
+ <div class="card-body">
70
+ <h5 class="card-title">CPU Load</h5>
71
+ <p class="metric-value" id="cpu-load">0%</p>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ <div class="col-md-4">
76
+ <div class="card text-center">
77
+ <div class="card-body">
78
+ <h5 class="card-title">Memory Load</h5>
79
+ <p class="metric-value" id="mem-load">0%</p>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="card mt-4">
86
+ <div class="card-header bg-primary text-white">
87
+ <h5 class="mb-0">User Statistics</h5>
88
+ </div>
89
+ <div class="card-body">
90
+ <table class="table table-striped">
91
+ <thead>
92
+ <tr>
93
+ <th>User Name</th>
94
+ <th>Active Connections</th>
95
+ <th>Total Connects</th>
96
+ <th>Data Transferred</th>
97
+ </tr>
98
+ </thead>
99
+ <tbody id="user-table-body">
100
+ <!-- Filled by JS -->
101
+ </tbody>
102
+ </table>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </body>
107
+ </html>
108
+ """
109
+
110
+ @app.route('/')
111
+ def index():
112
+ return render_template_string(HTML_TEMPLATE)
113
+
114
+ @app.route('/api/stats')
115
+ def stats():
116
+ user_stats = {}
117
+ try:
118
+ # mtprotoproxy.user_stats is a defaultdict of Counter
119
+ # Need to copy list of items first since dictionary can change size during iteration
120
+ items = list(mtprotoproxy.user_stats.items())
121
+ for user, counter in items:
122
+ user_stats[user] = dict(counter)
123
+ except Exception as e:
124
+ print(f"Error reading stats: {e}")
125
+
126
+ return jsonify({
127
+ "cpu_percent": psutil.cpu_percent(interval=None),
128
+ "mem_percent": psutil.virtual_memory().percent,
129
+ "user_stats": user_stats
130
+ })
131
+
132
+ def start_webui():
133
+ # Make sure we don't start the reloader, as we're running alongside asyncio loop
134
+ app.run(host='0.0.0.0', port=7860, debug=False, use_reloader=False)
135
+
136
+ if __name__ == "__main__":
137
+ start_webui()