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
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 ;
160174handle_info ({tcp_error , Sock , Reason }, # state {sock = Sock } = State ) ->
161175 {stop , {downstream_tcp_error , Reason }, State };
162176handle_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 ;
288308handle_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 ;
290313handle_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) ->
472495tcp_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 };
0 commit comments