Skip to content

Commit ed2fa40

Browse files
seriypsCopilot
andcommitted
Add TCP keepalive tuning and 4-element UpsStatic
- TCP keepalive on downstream connections (KEEPIDLE=40s, KEEPINTVL=40s, KEEPCNT=5): matches reference C implementation (net-events.c). Uses raw socket options for OTP 25+ compatibility (named keepidle/ keepintvl/keepcnt require OTP 28.3+/Kernel 10.5). - Expand UpsStatic to 4-element tuple {ConnId, Addr, AdTag, PacketLayer} and thread packet_layer through mtp_handler -> mtp_dc_pool -> mtp_down_conn. Fixes pattern match in handle_upstream_closed. - Reduce log noise for expected DC connection rotation: When Telegram closes a downstream connection with no active clients, stop with {shutdown, downstream_socket_closed} instead of bare atom; mtp_down_conn logs INFO, mtp_dc_pool logs INFO (was ERROR for all). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 84cceae commit ed2fa40

3 files changed

Lines changed: 52 additions & 15 deletions

File tree

src/mtp_dc_pool.erl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ dc_to_pool_name(DcId) ->
7272
binary_to_atom(<<"mtp_dc_pool_", (integer_to_binary(DcId))/binary>>, utf8).
7373

7474
-spec get(pid(), upstream(), #{addr := mtp_config:netloc_v4v6(),
75-
ad_tag => binary()}) -> downstream() | {error, atom()}.
75+
ad_tag => binary(),
76+
packet_layer => mtp_down_conn:packet_layer()}) -> downstream() | {error, atom()}.
7677
get(Pool, Upstream, #{addr := _} = Opts) ->
7778
gen_server:call(Pool, {get, Upstream, Opts}).
7879

@@ -192,7 +193,12 @@ handle_down(MonRef, Pid, Reason, #state{downstreams = Ds,
192193
{Pid, DsM1} ->
193194
Pending1 = lists:delete(Pid, Pending),
194195
Ds1 = ds_remove(Pid, Ds),
195-
?LOG_ERROR("Downstream=~p is down. reason=~p", [Pid, Reason]),
196+
case Reason of
197+
{shutdown, downstream_socket_closed} ->
198+
?LOG_INFO("Downstream=~p closed (no active clients)", [Pid]);
199+
_ ->
200+
?LOG_ERROR("Downstream=~p is down. reason=~p", [Pid, Reason])
201+
end,
196202
maybe_restart_connection(
197203
St#state{pending_downstreams = Pending1,
198204
downstreams = Ds1,

src/mtp_down_conn.erl

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
%% gen_server callbacks
2626
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
2727
terminate/2, code_change/3]).
28-
-export_type([handle/0, upstream_opts/0]).
28+
-export_type([handle/0, upstream_opts/0, packet_layer/0]).
2929

3030
-include_lib("kernel/include/logger.hrl").
3131

@@ -38,13 +38,24 @@
3838
-define(MAX_CODEC_BUFFERS, 5 * 1024 * 1024).
3939
-define(DEFAULT_CLIENTS_PER_CONN, 300).
4040

41+
%% Linux raw socket option constants for TCP keepalive tuning.
42+
%% Named options (keepidle/keepintvl/keepcnt) require OTP 28.3+ (Kernel 10.5);
43+
%% raw options work on OTP 25+ on any Linux kernel.
44+
-define(IPPROTO_TCP, 6).
45+
-define(TCP_KEEPIDLE, 4).
46+
-define(TCP_KEEPINTVL, 5).
47+
-define(TCP_KEEPCNT, 6).
48+
4149
-type handle() :: pid().
50+
-type packet_layer() :: mtp_abridged | mtp_intermediate | mtp_secure.
4251
-type upstream_opts() :: #{addr := mtp_config:netloc_v4v6(), % IP/Port of TG client
43-
ad_tag => binary()}.
52+
ad_tag => binary(),
53+
packet_layer => packet_layer()}.
4454
-type upstream() :: {
4555
_UpsStatic ::{_ConnId :: mtp_rpc:conn_id(),
4656
_Addr :: binary(),
47-
_AdTag :: binary() | undefined},
57+
_AdTag :: binary() | undefined,
58+
_Protocol :: mtp_abridged | mtp_intermediate | mtp_secure},
4859
_NonAckCount :: non_neg_integer(),
4960
_NonAckBytes :: non_neg_integer()
5061
}.
@@ -155,8 +166,11 @@ handle_info({tcp, Sock, Data}, #state{sock = Sock, dc_id = DcId} = S) ->
155166
{ok, S1} = handle_downstream_data(Data, S),
156167
activate_if_no_overflow(S1),
157168
{noreply, S1};
158-
handle_info({tcp_closed, Sock}, #state{sock = Sock} = State) ->
159-
{stop, downstream_socket_closed, State};
169+
handle_info({tcp_closed, Sock}, #state{sock = Sock, upstreams = Ups} = State) ->
170+
case map_size(Ups) of
171+
0 -> {stop, {shutdown, downstream_socket_closed}, State};
172+
_ -> {stop, downstream_socket_closed, State}
173+
end;
160174
handle_info({tcp_error, Sock, Reason}, #state{sock = Sock} = State) ->
161175
{stop, {downstream_tcp_error, Reason}, State};
162176
handle_info(do_connect, #state{dc_id = DcId} = State) ->
@@ -180,10 +194,15 @@ handle_info(handshake_timeout, #state{stage = Stage, dc_id = DcId} = St) ->
180194
end.
181195

182196

183-
terminate(_Reason, #state{upstreams = Ups}) ->
184-
%% Should I do this or dc_pool? Maybe only when reason is 'normal'?
185-
?LOG_WARNING("Downstream terminates with reason ~p; len(upstreams)=~p",
186-
[_Reason, map_size(Ups)]),
197+
terminate(Reason, #state{upstreams = Ups}) ->
198+
NUps = map_size(Ups),
199+
case Reason of
200+
{shutdown, downstream_socket_closed} ->
201+
?LOG_INFO("Downstream closed (no active clients); len(upstreams)=~p", [NUps]);
202+
_ ->
203+
?LOG_WARNING("Downstream terminates with reason ~p; len(upstreams)=~p",
204+
[Reason, NUps])
205+
end,
187206
Self = self(),
188207
lists:foreach(
189208
fun(Upstream) ->
@@ -211,7 +230,8 @@ handle_upstream_new(Upstream, Opts, #state{upstreams = Ups,
211230
ConnId = erlang:unique_integer(),
212231
{Ip, Port} = maps:get(addr, Opts),
213232
AdTag = maps:get(ad_tag, Opts, undefined),
214-
UpsStatic = {ConnId, iolist_to_binary(mtp_rpc:encode_ip_port(Ip, Port)), AdTag},
233+
PacketLayer = maps:get(packet_layer, Opts, mtp_abridged),
234+
UpsStatic = {ConnId, iolist_to_binary(mtp_rpc:encode_ip_port(Ip, Port)), AdTag, PacketLayer},
215235
Ups1 = Ups#{Upstream => {UpsStatic, 0, 0}},
216236
UpsRev1 = UpsRev#{ConnId => Upstream},
217237
?LOG_DEBUG("New upstream=~p conn_id=~p", [Upstream, ConnId]),
@@ -223,7 +243,7 @@ handle_upstream_closed(Upstream, #state{upstreams = Ups,
223243
upstreams_rev = UpsRev} = St) ->
224244
%% See "mtproto-proxy.c:remove_ext_connection
225245
case maps:take(Upstream, Ups) of
226-
{{{ConnId, _, _}, _, _}, Ups1} ->
246+
{{{ConnId, _, _, _}, _, _}, Ups1} ->
227247
St1 = non_ack_cleanup_upstream(Upstream, St),
228248
UpsRev1 = maps:remove(ConnId, UpsRev),
229249
St2 = St1#state{upstreams = Ups1,
@@ -287,6 +307,9 @@ handle_rpc({close_ext, ConnId}, St) ->
287307
end;
288308
handle_rpc({simple_ack, ConnId, Confirm}, S) ->
289309
up_send({simple_ack, self(), Confirm}, ConnId, S);
310+
handle_rpc({ping, PingId}, S) ->
311+
{ok, S1} = down_send(mtp_rpc:encode_packet({pong, PingId}, undefined), S),
312+
S1;
290313
handle_rpc({unknown, Tag, Tail}, S) ->
291314
?LOG_INFO("Unknown packet from backend. Tag ~w, tail: ~w", [Tag, Tail]),
292315
S.
@@ -472,13 +495,20 @@ connect(DcId, S) ->
472495
tcp_connect(Host, Port) ->
473496
BufSize = application:get_env(?APP, downstream_socket_buffer_size,
474497
?MAX_SOCK_BUF_SIZE),
498+
%% TCP keepalive tuning: match reference implementations (MTProxy C, mtprotoproxy Python).
499+
%% Start probing after 40s idle, retry every 40s, drop after 5 failed probes.
500+
%% Raw options are used for OTP 25+ compatibility (named keepidle/keepintvl/keepcnt
501+
%% require OTP 28.3+ / Kernel 10.5).
475502
SockOpts = [{active, once},
476503
{packet, raw},
477504
{mode, binary},
478505
{buffer, BufSize},
479506
{send_timeout, ?SEND_TIMEOUT},
480507
%% {nodelay, true},
481-
{keepalive, true}],
508+
{keepalive, true},
509+
{raw, ?IPPROTO_TCP, ?TCP_KEEPIDLE, <<40:32/native>>},
510+
{raw, ?IPPROTO_TCP, ?TCP_KEEPINTVL, <<40:32/native>>},
511+
{raw, ?IPPROTO_TCP, ?TCP_KEEPCNT, <<5:32/native>>}],
482512
case gen_tcp:connect(Host, Port, SockOpts, ?CONN_TIMEOUT) of
483513
{ok, Sock} ->
484514
{ok, Sock};

src/mtp_handler.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,8 @@ parse_upstream_data(<<Header:64/binary, Rest/binary>>,
419419
Codec2 = mtp_codec:replace(packet, PacketLayerMod, PacketCodec, Codec1),
420420
Codec = mtp_codec:push_back(crypto, Rest, Codec2),
421421
Opts = #{ad_tag => Tag,
422-
addr => Addr},
422+
addr => Addr,
423+
packet_layer => PacketLayerMod},
423424
{RealDcId, Pool, Downstream} = mtp_config:get_downstream_safe(DcId, Opts),
424425
handle_upstream_data(
425426
<<>>,

0 commit comments

Comments
 (0)