Skip to content

Commit 4e0db66

Browse files
seriypsCopilot
andcommitted
mtp_config: parse default DC id from Telegram config; drop get_netloc_safe; ETS protected
- parse_config/1 now returns {DefaultDc, Downstreams} using a single lists:foldl pass; the 'default X;' line sets the fallback DC id - update_ids/3 stores {default_dc, DcId} in ETS alongside dc_ids - get_default_dc/0 reads default_dc from ETS (safe from any process) - get_downstream_safe/2 fallback uses get_default_dc() instead of random_choice; errors immediately if default == requested (avoid loop) - get_netloc_safe/1 removed: dead code since 2018, never called - ETS table changed from public to protected (only mtp_config writes) - doc/handler-downstream-flow.md: new sequence diagram + update note about pool resolution fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8ce8f21 commit 4e0db66

2 files changed

Lines changed: 89 additions & 31 deletions

File tree

doc/handler-downstream-flow.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Handler ↔ downstream lookup and handshake
2+
3+
Shows how `mtp_handler` locates an `mtp_down_conn` for a new client connection
4+
and the steady-state data flow that follows.
5+
6+
**Key actors:**
7+
- `mtp_handler` — one process per Telegram client TCP connection
8+
- `mtp_dc_pool` — manages a pool of downstream connections for one DC
9+
- `mtp_down_conn` — multiplexed TCP connection to a Telegram DC
10+
- `Telegram DC` — the upstream Telegram data-centre server
11+
12+
```mermaid
13+
sequenceDiagram
14+
participant Client as Telegram client
15+
participant Handler as mtp_handler
16+
participant Pool as mtp_dc_pool
17+
participant Down as mtp_down_conn
18+
participant TG as Telegram DC
19+
20+
Client->>Handler: TCP connect + Hello bytes
21+
22+
Note over Handler: decode protocol headers<br/>(fake-TLS / obfuscated / secure)<br/>stage: hello → tunnel
23+
24+
Note over Handler: resolve pool: whereis(dc_to_pool_name(DcId))<br/>(registered name lookup; falls back to default DC from mtp_config if not found)
25+
Handler->>Pool: mtp_dc_pool:get(Pool, self(), Opts) [sync]
26+
Pool-->>Down: upstream_new(Handler, Opts) [cast]
27+
Pool->>Handler: Downstream pid
28+
29+
Note over Handler: down = Downstream<br/>stage = tunnel
30+
31+
loop steady-state data exchange
32+
Client->>Handler: TCP data
33+
Handler->>Down: mtp_down_conn:send(Down, Data) [sync]
34+
Down->>TG: TCP data (RPC-framed)
35+
TG->>Down: TCP data
36+
Down->>Handler: ok
37+
Down-->>Handler: {proxy_ans, Down, Data} [cast]
38+
Handler->>Client: TCP data
39+
Handler-->>Down: mtp_down_conn:ack(Down, Count, Size) [cast]
40+
end
41+
42+
Client->>Handler: TCP close
43+
Handler-->>Pool: mtp_dc_pool:return(Pool, self()) [cast]
44+
Pool-->>Down: upstream_closed(Down, Handler) [cast]
45+
```

src/mtp_config.erl

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
-export([get_downstream_safe/2,
1919
get_downstream_pool/1,
2020
get_netloc/1,
21-
get_netloc_safe/1,
2221
get_secret/0,
22+
get_default_dc/0,
2323
status/0,
2424
update/0]).
2525

@@ -37,6 +37,7 @@
3737
-define(TAB, ?MODULE).
3838
-define(IPS_KEY(DcId), {id, DcId}).
3939
-define(IDS_KEY, dc_ids).
40+
-define(DEFAULT_DC_KEY, default_dc).
4041
-define(SECRET_URL, "https://core.telegram.org/getProxySecret").
4142
-define(CONFIG_URL, "https://core.telegram.org/getProxyConfig").
4243

@@ -65,9 +66,11 @@ get_downstream_safe(DcId, Opts) ->
6566
error({pool_empty, DcId, Pool})
6667
end;
6768
not_found ->
68-
[{?IDS_KEY, L}] = ets:lookup(?TAB, ?IDS_KEY),
69-
NewDcId = random_choice(L),
70-
get_downstream_safe(NewDcId, Opts)
69+
case get_default_dc() of
70+
undefined -> error({no_pool, DcId});
71+
DcId -> error({no_pool, DcId}); % default == requested, avoid loop
72+
NewDcId -> get_downstream_safe(NewDcId, Opts)
73+
end
7174
end.
7275

7376
get_downstream_pool(DcId) ->
@@ -78,17 +81,6 @@ get_downstream_pool(DcId) ->
7881
not_found
7982
end.
8083

81-
-spec get_netloc_safe(dc_id()) -> {dc_id(), netloc()}.
82-
get_netloc_safe(DcId) ->
83-
case get_netloc(DcId) of
84-
{ok, Addr} -> {DcId, Addr};
85-
not_found ->
86-
[{?IDS_KEY, L}] = ets:lookup(?TAB, ?IDS_KEY),
87-
NewDcId = random_choice(L),
88-
%% Get random DC; it might return 0 and recurse aggain
89-
get_netloc_safe(NewDcId)
90-
end.
91-
9284
get_netloc(DcId) ->
9385
Key = ?IPS_KEY(DcId),
9486
case ets:lookup(?TAB, Key) of
@@ -107,6 +99,13 @@ get_secret() ->
10799
[{_, Key}] = ets:lookup(?TAB, key),
108100
Key.
109101

102+
-spec get_default_dc() -> dc_id() | undefined.
103+
get_default_dc() ->
104+
case ets:lookup(?TAB, ?DEFAULT_DC_KEY) of
105+
[{?DEFAULT_DC_KEY, DcId}] -> DcId;
106+
[] -> undefined
107+
end.
108+
110109
-spec status() -> [mtp_dc_pool:status()].
111110
status() ->
112111
[{?IDS_KEY, L}] = ets:lookup(?TAB, ?IDS_KEY),
@@ -130,7 +129,7 @@ init([]) ->
130129
#{timeout => {env, ?APP, conf_refresh_interval, 3600},
131130
unit => second}),
132131
Tab = ets:new(?TAB, [set,
133-
public,
132+
protected,
134133
named_table,
135134
{read_concurrency, true}]),
136135
State = #state{tab = Tab,
@@ -189,17 +188,23 @@ update_key(Tab) ->
189188
update_config(Tab) ->
190189
Url = application:get_env(mtproto_proxy, proxy_config_url, ?CONFIG_URL),
191190
{ok, Body} = http_get(Url),
192-
Downstreams = parse_config(Body),
191+
{DefaultDc, Downstreams} = parse_config(Body),
193192
update_downstreams(Downstreams, Tab),
194-
update_ids(Downstreams, Tab).
193+
update_ids(Downstreams, DefaultDc, Tab).
195194

196195
parse_config(Body) ->
197196
Lines = string:lexemes(Body, "\n"),
198-
ProxyLines = lists:filter(
199-
fun("proxy_for " ++ _) -> true;
200-
(_) -> false
201-
end, Lines),
202-
[parse_downstream(Line) || Line <- ProxyLines].
197+
{DefaultDc, Downstreams} =
198+
lists:foldl(
199+
fun("default " ++ _ = Line, {_, Ds}) ->
200+
["default", DcIdStr] = string:lexemes(Line, " ;"),
201+
{list_to_integer(DcIdStr), Ds};
202+
("proxy_for " ++ _ = Line, {Def, Ds}) ->
203+
{Def, [parse_downstream(Line) | Ds]};
204+
(_, Acc) ->
205+
Acc
206+
end, {undefined, []}, Lines),
207+
{DefaultDc, lists:reverse(Downstreams)}.
203208

204209
parse_downstream(Line) ->
205210
["proxy_for",
@@ -231,9 +236,10 @@ update_downstreams(Downstreams, Tab) ->
231236
end,
232237
maps:keys(ByDc)).
233238

234-
update_ids(Downstreams, Tab) ->
239+
update_ids(Downstreams, DefaultDc, Tab) ->
235240
Ids = lists:usort([DcId || {DcId, _, _} <- Downstreams]),
236-
true = ets:insert(Tab, {?IDS_KEY, Ids}).
241+
true = ets:insert(Tab, {?IDS_KEY, Ids}),
242+
true = ets:insert(Tab, {?DEFAULT_DC_KEY, DefaultDc}).
237243

238244
update_ip() ->
239245
case application:get_env(?APP, ip_lookup_services) of
@@ -283,15 +289,22 @@ random_choice(L) ->
283289
-include_lib("eunit/include/eunit.hrl").
284290

285291
parse_test() ->
286-
Config = ("# force_probability 1 10
287-
proxy_for 1 149.154.175.50:8888;
288-
proxy_for -1 149.154.175.50:8888;
289-
proxy_for 2 149.154.162.39:80;
290-
proxy_for 2 149.154.162.33:80;"),
292+
Config = ("# force_probability 1 10\n"
293+
"default 2;\n"
294+
"proxy_for 1 149.154.175.50:8888;\n"
295+
"proxy_for -1 149.154.175.50:8888;\n"
296+
"proxy_for 2 149.154.162.39:80;\n"
297+
"proxy_for 2 149.154.162.33:80;"),
291298
Expect = [{1, {149, 154, 175, 50}, 8888},
292299
{-1, {149, 154, 175, 50}, 8888},
293300
{2, {149, 154, 162, 39}, 80},
294301
{2, {149, 154, 162, 33},80}],
295-
?assertEqual(Expect, parse_config(Config)).
302+
?assertEqual({2, Expect}, parse_config(Config)).
303+
304+
parse_no_default_test() ->
305+
Config = ("proxy_for 1 149.154.175.50:8888;\n"
306+
"proxy_for 2 149.154.162.39:80;"),
307+
{DefaultDc, _} = parse_config(Config),
308+
?assertEqual(undefined, DefaultDc).
296309

297310
-endif.

0 commit comments

Comments
 (0)