mirror of
https://github.com/seriyps/mtproto_proxy.git
synced 2026-08-04 15:19:47 +00:00
Initial implementation
This commit is contained in:
commit
d2fab355bf
12 changed files with 927 additions and 0 deletions
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.rebar3
|
||||
_*
|
||||
.eunit
|
||||
*.o
|
||||
*.beam
|
||||
*.plt
|
||||
*.swp
|
||||
*.swo
|
||||
.erlang.cookie
|
||||
ebin
|
||||
log
|
||||
erl_crash.dump
|
||||
.rebar
|
||||
logs
|
||||
_build
|
||||
.idea
|
||||
*.iml
|
||||
rebar3.crashdump
|
||||
*~
|
||||
config/prod*
|
||||
config/proxy*
|
||||
deploy/*.retry
|
||||
167
LICENSE.txt
Normal file
167
LICENSE.txt
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
52
README.md
Normal file
52
README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
Erlang mtproto proxy
|
||||
====================
|
||||
|
||||
This part of code was extracted from [@socksy_bot](https://t.me/socksy_bot).
|
||||
|
||||
How to start
|
||||
------------
|
||||
|
||||
Install deps (ubuntu 18.04)
|
||||
|
||||
```
|
||||
sudo apt install erlang-nox erlang-dev build-essential
|
||||
```
|
||||
|
||||
Compile:
|
||||
|
||||
```
|
||||
./rebar3 release
|
||||
```
|
||||
|
||||
Start with interactive console:
|
||||
|
||||
```
|
||||
./_build/default/rel/mtp_proxy/bin/mtp_proxy console
|
||||
```
|
||||
|
||||
Start in foreground
|
||||
|
||||
```
|
||||
./_build/default/rel/mtp_proxy/bin/mtp_proxy foreground
|
||||
```
|
||||
|
||||
Start in background
|
||||
|
||||
```
|
||||
./_build/default/rel/mtp_proxy/bin/mtp_proxy start
|
||||
```
|
||||
|
||||
Stop proxy started in background
|
||||
|
||||
```
|
||||
./_build/default/rel/mtp_proxy/bin/mtp_proxy stop
|
||||
```
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
||||
Number of connections in background mode
|
||||
|
||||
```
|
||||
./_build/default/rel/mtp_proxy/bin/mtp_proxy eval 'lists:sum([proplists:get_value(all_connections, L) || {_, L} <- ranch:info()]).'
|
||||
```
|
||||
22
rebar.config
Normal file
22
rebar.config
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
% -*- mode: erlang -*-
|
||||
{erl_opts, [debug_info,
|
||||
{parse_transform, lager_transform}]}.
|
||||
|
||||
{deps, [{ranch, "1.5.0"},
|
||||
{lager, "3.6.1"}
|
||||
]}.
|
||||
|
||||
{relx, [{release, { mtp_proxy, "0.1.0" },
|
||||
[lager,
|
||||
ranch,
|
||||
mtproto_proxy,
|
||||
sasl]},
|
||||
|
||||
%% {sys_config, "./config/sys.config"},
|
||||
%% {vm_args, "./config/vm.args"},
|
||||
|
||||
{dev_mode, false},
|
||||
{include_erts, false},
|
||||
|
||||
{extended_start_script, true}]
|
||||
}.
|
||||
10
rebar.lock
Normal file
10
rebar.lock
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{"1.1.0",
|
||||
[{<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1},
|
||||
{<<"lager">>,{pkg,<<"lager">>,<<"3.6.1">>},0},
|
||||
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.5.0">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>},
|
||||
{<<"lager">>, <<"9D29C5FF7F926D25ECD9899990867C9152DCF34EEE65BAC8EC0DFC0D16A26E0C">>},
|
||||
{<<"ranch">>, <<"F04166F456790FEE2AC1AA05A02745CC75783C2BFB26D39FAF6AEFC9A3D3A58A">>}]}
|
||||
].
|
||||
BIN
rebar3
Executable file
BIN
rebar3
Executable file
Binary file not shown.
152
src/gen_timeout.erl
Normal file
152
src/gen_timeout.erl
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
%%% @author Sergey Prokhorov <me@seriyps.ru>
|
||||
%%% @copyright (C) 2018, Sergey Prokhorov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 9 Apr 2018 by Sergey Prokhorov <me@seriyps.ru>
|
||||
|
||||
-module(gen_timeout).
|
||||
|
||||
-export([new/1,
|
||||
set_timeout/2,
|
||||
bump/1,
|
||||
reset/1,
|
||||
is_expired/1,
|
||||
time_to_message/1,
|
||||
time_left/1]).
|
||||
-export([upgrade/1]).
|
||||
-export_type([tout/0, opts/0]).
|
||||
|
||||
-record(timeout,
|
||||
{ref :: reference(),
|
||||
last_bump :: integer(),
|
||||
message :: any(),
|
||||
unit = second :: erlang:time_unit(),
|
||||
timeout :: timeout_type()}).
|
||||
|
||||
-type timeout_type() ::
|
||||
non_neg_integer()
|
||||
| {env, App :: atom(), Name :: atom(), Default :: non_neg_integer()}.
|
||||
|
||||
-type opts() :: #{message => any(),
|
||||
unit => erlang:time_unit(),
|
||||
timeout := timeout_type()}.
|
||||
|
||||
-opaque tout() :: #timeout{}.
|
||||
|
||||
-define(MS_PER_SEC, 1000).
|
||||
|
||||
-spec new(opts()) -> tout().
|
||||
new(Opts) ->
|
||||
Default = #{message => timeout,
|
||||
unit => second},
|
||||
#{message := Message,
|
||||
timeout := Timeout,
|
||||
unit := Unit} = maps:merge(Default, Opts),
|
||||
%% TODO: get rid of 2 system_time/1 calls in `new + reset`
|
||||
reset(#timeout{message = Message,
|
||||
unit = Unit,
|
||||
last_bump = erlang:system_time(Unit),
|
||||
timeout = Timeout}).
|
||||
|
||||
-spec set_timeout(timeout_type(), tout()) -> tout().
|
||||
set_timeout(Timeout, S) ->
|
||||
reset(S#timeout{timeout = Timeout}).
|
||||
|
||||
-spec bump(tout()) -> tout().
|
||||
bump(#timeout{unit = Unit} = S) ->
|
||||
S#timeout{last_bump = erlang:system_time(Unit)}.
|
||||
|
||||
-spec reset(tout()) -> tout().
|
||||
reset(#timeout{ref = Ref, message = Message, unit = Unit} = S) ->
|
||||
(is_reference(Ref))
|
||||
andalso erlang:cancel_timer(Ref),
|
||||
SendAfter = max(time_left(S), 0),
|
||||
After = erlang:convert_time_unit(SendAfter, Unit, millisecond),
|
||||
Ref1 = erlang:send_after(After, self(), Message),
|
||||
S#timeout{ref = Ref1}.
|
||||
|
||||
-spec is_expired(tout()) -> boolean().
|
||||
is_expired(S) ->
|
||||
time_left(S) =< 0.
|
||||
|
||||
-spec time_to_message(tout()) -> non_neg_integer() | false.
|
||||
time_to_message(#timeout{ref = Ref}) ->
|
||||
erlang:read_timer(Ref).
|
||||
|
||||
-spec time_left(tout()) -> integer().
|
||||
time_left(#timeout{last_bump = LastBump, unit = Unit} = S) ->
|
||||
Timeout = get_timeout(S),
|
||||
Now = erlang:system_time(Unit),
|
||||
ExpiresAt = LastBump + Timeout,
|
||||
ExpiresAt - Now.
|
||||
|
||||
upgrade({timeout, Ref, LastBump, Message, Timeout}) ->
|
||||
Timeout1 = case Timeout of
|
||||
{sec, Val} -> Val;
|
||||
_ -> Timeout
|
||||
end,
|
||||
#timeout{ref = Ref,
|
||||
last_bump = LastBump,
|
||||
message = Message,
|
||||
timeout = Timeout1,
|
||||
unit = second}.
|
||||
|
||||
%% Internal
|
||||
|
||||
get_timeout(#timeout{timeout = {env, App, Name, Default}}) ->
|
||||
application:get_env(App, Name, Default);
|
||||
get_timeout(#timeout{timeout = Sec}) ->
|
||||
Sec.
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
new_expire_test() ->
|
||||
T = new(#{timeout => 100,
|
||||
unit => millisecond,
|
||||
message => ?FUNCTION_NAME}),
|
||||
?assertNot(is_expired(T)),
|
||||
?assert(time_left(T) > 0),
|
||||
?assert(time_to_message(T) > 0),
|
||||
ok= recv(?FUNCTION_NAME),
|
||||
?assert(time_left(T) =< 0),
|
||||
?assert(is_expired(T)).
|
||||
|
||||
reset_test() ->
|
||||
T = new(#{timeout => 100,
|
||||
unit => millisecond,
|
||||
message => ?FUNCTION_NAME}),
|
||||
?assertNot(is_expired(T)),
|
||||
T1 = reset(T),
|
||||
?assertNot(is_expired(T1)),
|
||||
ok = recv(?FUNCTION_NAME),
|
||||
?assert(is_expired(T1)).
|
||||
|
||||
bump_test() ->
|
||||
T = new(#{timeout => 1000,
|
||||
unit => millisecond,
|
||||
message => ?FUNCTION_NAME}),
|
||||
?assertNot(is_expired(T)),
|
||||
TimeToMessage0 = time_to_message(T),
|
||||
timer:sleep(600),
|
||||
T1 = bump(T),
|
||||
?assert((TimeToMessage0 - 600) >= time_to_message(T1),
|
||||
"Bump doesn't affect timer message"),
|
||||
timer:sleep(500),
|
||||
%% Got message, but not yet expired
|
||||
?assertEqual(false, time_to_message(T1)),
|
||||
?assertNot(is_expired(T1)),
|
||||
ok = recv(?FUNCTION_NAME),
|
||||
?assertNot(is_expired(T1)),
|
||||
T2 = reset(T1),
|
||||
ok = recv(?FUNCTION_NAME),
|
||||
?assert(is_expired(T2)).
|
||||
|
||||
recv(What) ->
|
||||
receive What -> ok
|
||||
after 5000 ->
|
||||
error({timeout, What})
|
||||
end.
|
||||
|
||||
-endif.
|
||||
268
src/mtp_handler.erl
Normal file
268
src/mtp_handler.erl
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
%%% @author Sergey Prokhorov <me@seriyps.ru>
|
||||
%%% @copyright (C) 2018, Sergey Prokhorov
|
||||
%%% @doc
|
||||
%%% MTProto proxy network layer
|
||||
%%% @end
|
||||
%%% Created : 9 Apr 2018 by Sergey Prokhorov <me@seriyps.ru>
|
||||
|
||||
-module(mtp_handler).
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ranch_protocol).
|
||||
|
||||
%% API
|
||||
-export([start_link/4]).
|
||||
-export([hex/1]).
|
||||
-export([key_str/0]).
|
||||
|
||||
%% Callbacks
|
||||
-export([ranch_init/1]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-define(MAX_SOCK_BUF_SIZE, 1024 * 300). % Decrease if CPU is cheaper than RAM
|
||||
-define(APP, mtproto_proxy).
|
||||
|
||||
-record(state,
|
||||
{stage = init :: stage(),
|
||||
init_buf = <<>> :: binary(),
|
||||
up_sock :: gen_tcp:socket(),
|
||||
up_transport :: transport(),
|
||||
up_codec :: mtp_obfuscated:codec(),
|
||||
down_sock :: gen_tcp:socket(),
|
||||
started :: pos_integer(),
|
||||
timer_state = init :: init | hibernate | stop,
|
||||
timer :: gen_timeout:tout()}).
|
||||
|
||||
-type transport() :: module().
|
||||
-type stage() :: init | tunnel.
|
||||
|
||||
|
||||
%% APIs
|
||||
|
||||
start_link(Ref, Socket, Transport, Opts) ->
|
||||
{ok, proc_lib:spawn_link(?MODULE, ranch_init, [{Ref, Socket, Transport, Opts}])}.
|
||||
|
||||
key_str() ->
|
||||
{ok, Secret} = application:get_env(?APP, secret),
|
||||
hex(Secret).
|
||||
|
||||
%% Callbacks
|
||||
|
||||
%% Custom gen_server init
|
||||
ranch_init({Ref, Socket, Transport, _} = Opts) ->
|
||||
case init(Opts) of
|
||||
{ok, State} ->
|
||||
ok = ranch:accept_ack(Ref),
|
||||
ok = Transport:setopts(Socket,
|
||||
[{active, once},
|
||||
{buffer, ?MAX_SOCK_BUF_SIZE}
|
||||
]),
|
||||
gen_server:enter_loop(?MODULE, [], State);
|
||||
error ->
|
||||
exit(normal)
|
||||
end.
|
||||
|
||||
init({_Ref, Socket, Transport, _}) ->
|
||||
case Transport:peername(Socket) of
|
||||
{ok, {Ip, Port}} ->
|
||||
lager:info("New connection ~s:~p", [inet:ntoa(Ip), Port]),
|
||||
{TimeoutKey, TimeoutDefault} = state_timeout(init),
|
||||
Timer = gen_timeout:new(
|
||||
#{timeout => {env, ?APP, TimeoutKey, TimeoutDefault}}),
|
||||
State = #state{up_sock = Socket,
|
||||
up_transport = Transport,
|
||||
started = erlang:system_time(second),
|
||||
timer = Timer},
|
||||
{ok, State};
|
||||
{error, Reason} ->
|
||||
lager:info("Can't read peername: ~p", [Reason]),
|
||||
error
|
||||
end.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({tcp, Sock, Data}, #state{up_sock = Sock,
|
||||
up_transport = Transport} = S) ->
|
||||
%% client -> proxy
|
||||
case handle_upstream_data(Data, S) of
|
||||
{ok, S1} ->
|
||||
ok = Transport:setopts(Sock, [{active, once}]),
|
||||
{noreply, bump_timer(S1)};
|
||||
{error, Reason} ->
|
||||
lager:info("handle_data error ~p", [Reason]),
|
||||
{stop, normal, S}
|
||||
end;
|
||||
handle_info({tcp_closed, Sock}, #state{up_sock = Sock} = S) ->
|
||||
lager:debug("upstream sock closed"),
|
||||
{stop, normal, maybe_close_out(S)};
|
||||
handle_info({tcp_error, Sock, Reason}, #state{up_sock = Sock} = S) ->
|
||||
lager:info("upstream sock error: ~p", [Reason]),
|
||||
{stop, Reason, maybe_close_out(S)};
|
||||
|
||||
handle_info({tcp, Sock, Data}, #state{down_sock = Sock} = S) ->
|
||||
%% telegram server -> proxy
|
||||
case handle_downstream_data(Data, S) of
|
||||
{ok, S1} ->
|
||||
ok = inet:setopts(Sock, [{active, once}]),
|
||||
{noreply, bump_timer(S1)};
|
||||
{error, Reason} ->
|
||||
lager:error("Error sending tunnelled data to in socket: ~p", [Reason]),
|
||||
{stop, normal, S}
|
||||
end;
|
||||
handle_info({tcp_closed, Sock}, #state{down_sock = Sock,
|
||||
up_sock = ISock, up_transport = ITrans} = S) ->
|
||||
lager:debug("downstream sock closed"),
|
||||
ok = ITrans:close(ISock),
|
||||
{stop, normal, S};
|
||||
handle_info({tcp_error, Sock, Reason}, #state{down_sock = Sock,
|
||||
up_sock = ISock, up_transport = ITrans} = S) ->
|
||||
lager:info("downstream sock error: ~p", [Reason]),
|
||||
ok = ITrans:close(ISock),
|
||||
{stop, Reason, S};
|
||||
|
||||
|
||||
handle_info(timeout, #state{timer = Timer, timer_state = TState} = S) ->
|
||||
case gen_timeout:is_expired(Timer) of
|
||||
true when TState == stop;
|
||||
TState == init ->
|
||||
lager:info("inactive timeout in state ~p", [TState]),
|
||||
{stop, normal, S};
|
||||
true when TState == hibernate ->
|
||||
{noreply, switch_timer(S, stop), hibernate};
|
||||
false ->
|
||||
Timer1 = gen_timeout:reset(Timer),
|
||||
{noreply, S#state{timer = Timer1}}
|
||||
end;
|
||||
handle_info(Other, S) ->
|
||||
lager:warning("Unexpected handle_info ~p", [Other]),
|
||||
{noreply, S}.
|
||||
|
||||
terminate(_Reason, #state{}) ->
|
||||
lager:debug("terminate ~p", [_Reason]),
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
maybe_close_out(#state{down_sock = undefined} = S) -> S;
|
||||
maybe_close_out(#state{down_sock = Out} = S) ->
|
||||
gen_tcp:close(Out),
|
||||
S#state{down_sock = undefined}.
|
||||
|
||||
bump_timer(#state{timer = Timer, timer_state = TState} = S) ->
|
||||
Timer1 = gen_timeout:bump(Timer),
|
||||
case TState of
|
||||
stop ->
|
||||
switch_timer(S#state{timer = Timer1}, hibernate);
|
||||
_ ->
|
||||
S#state{timer = Timer1}
|
||||
end.
|
||||
|
||||
switch_timer(#state{timer_state = TState} = S, TState) ->
|
||||
S;
|
||||
switch_timer(#state{timer_state = _FromState, timer = Timer} = S, ToState) ->
|
||||
{NewTimeKey, NewTimeDefault} = state_timeout(ToState),
|
||||
Timer1 = gen_timeout:set_timeout(
|
||||
{env, ?APP, NewTimeKey, NewTimeDefault}, Timer),
|
||||
S#state{timer_state = ToState,
|
||||
timer = Timer1}.
|
||||
|
||||
state_timeout(init) ->
|
||||
{init_timeout_sec, 60};
|
||||
state_timeout(hibernate) ->
|
||||
{hibernate_timeout_sec, 60};
|
||||
state_timeout(stop) ->
|
||||
{ready_timeout_sec, 1200}.
|
||||
|
||||
|
||||
%% Stream handlers
|
||||
|
||||
%% Handle telegram client -> proxy stream
|
||||
handle_upstream_data(<<Header:64/binary, Rest/binary>>, #state{stage = init, init_buf = <<>>} = S) ->
|
||||
{ok, Secret} = application:get_env(?APP, secret),
|
||||
case mtp_obfuscated:from_header(Header, Secret) of
|
||||
{ok, Endpoint, Codec} ->
|
||||
case handle_upstream_header(Endpoint, Codec, S) of
|
||||
{ok, S1} ->
|
||||
handle_upstream_data(Rest, S1);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
handle_upstream_data(Bin, #state{stage = init, init_buf = <<>>} = S) ->
|
||||
{ok, S#state{init_buf = Bin}};
|
||||
handle_upstream_data(Bin, #state{stage = init, init_buf = Buf} = S) ->
|
||||
handle_upstream_data(<<Buf/binary, Bin/binary>> , S#state{init_buf = <<>>});
|
||||
handle_upstream_data(Bin, #state{stage = tunnel,
|
||||
up_codec = UpCodec,
|
||||
down_sock = Sock} = S) ->
|
||||
{Decoded, UpCodec1} = mtp_obfuscated:decrypt(Bin, UpCodec),
|
||||
ok = gen_tcp:send(Sock, Decoded),
|
||||
{ok, S#state{up_codec = UpCodec1}}.
|
||||
|
||||
|
||||
%% Handle telegram server -> proxy stream
|
||||
handle_downstream_data(Bin, #state{stage = tunnel,
|
||||
up_codec = UpCodec,
|
||||
up_sock = Sock,
|
||||
up_transport = Transport} = S) ->
|
||||
{Encoded, UpCodec1} = mtp_obfuscated:encrypt(Bin, UpCodec),
|
||||
ok = Transport:send(Sock, Encoded),
|
||||
{ok, S#state{up_codec = UpCodec1}}.
|
||||
|
||||
|
||||
%% Packet handlers
|
||||
|
||||
|
||||
%% Internal
|
||||
|
||||
|
||||
handle_upstream_header(Endpoint, UpCodec, S) ->
|
||||
case connect(Endpoint, 443) of
|
||||
{ok, Sock} ->
|
||||
EndpointStr = inet:ntoa(Endpoint),
|
||||
lager:info("Connected to ~s:~p", [EndpointStr, 443]),
|
||||
ok = gen_tcp:send(Sock, <<239>>),
|
||||
{ok, S#state{stage = tunnel,
|
||||
down_sock = Sock,
|
||||
up_codec = UpCodec}};
|
||||
{error, _Reason} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-define(CONN_TIMEOUT, 10000).
|
||||
-define(SEND_TIMEOUT, 60 * 1000).
|
||||
|
||||
connect(Host, Port) ->
|
||||
SockOpts = [{active, once},
|
||||
{packet, raw},
|
||||
binary,
|
||||
{send_timeout, ?SEND_TIMEOUT},
|
||||
%% {nodelay, true},
|
||||
{keepalive, true}],
|
||||
case gen_tcp:connect(Host, Port, SockOpts, ?CONN_TIMEOUT) of
|
||||
{ok, Sock} ->
|
||||
ok = inet:setopts(Sock, [{buffer, ?MAX_SOCK_BUF_SIZE}]),
|
||||
{ok, Sock};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
|
||||
%% Internal
|
||||
|
||||
hex(Bin) ->
|
||||
[begin
|
||||
if N < 10 ->
|
||||
48 + N;
|
||||
true ->
|
||||
87 + N
|
||||
end
|
||||
end || <<N:4>> <= Bin].
|
||||
119
src/mtp_obfuscated.erl
Normal file
119
src/mtp_obfuscated.erl
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
%%% @author Sergey <me@seriyps.ru>
|
||||
%%% @copyright (C) 2018, Sergey
|
||||
%%% @doc
|
||||
%%% MTProto proxy encryption and packet layer; "obfuscated2" protocol lib
|
||||
%%% @end
|
||||
%%% Created : 29 May 2018 by Sergey <me@seriyps.ru>
|
||||
|
||||
-module(mtp_obfuscated).
|
||||
-export([new/0,
|
||||
new/1,
|
||||
from_header/2,
|
||||
encrypt/2,
|
||||
decrypt/2]).
|
||||
|
||||
-export_type([codec/0]).
|
||||
|
||||
-record(st,
|
||||
{encrypt :: any(), % aes state
|
||||
decrypt :: any() % aes state
|
||||
}).
|
||||
|
||||
-define(ENDPOINTS, {
|
||||
{149, 154, 175, 50},
|
||||
{149, 154, 167, 51},
|
||||
{149, 154, 175, 100},
|
||||
{149, 154, 167, 91},
|
||||
{149, 154, 171, 5}
|
||||
}).
|
||||
-define(APP, mtproto_proxy).
|
||||
%% -define(DBG(Fmt, Args), io:format(user, Fmt, Args)).
|
||||
-define(DBG(_F, _A), ok).
|
||||
|
||||
-opaque codec() :: #st{}.
|
||||
|
||||
%% @doc Creates new obfuscated stream (usual format)
|
||||
-spec new() -> {ok, Header :: binary(), codec()}.
|
||||
new() ->
|
||||
new(crypto:strong_rand_bytes(60)).
|
||||
|
||||
-spec new(binary()) -> {ok, Header :: binary(), codec()}.
|
||||
new(<<Left:56/binary, Right:4/binary>>) ->
|
||||
DownHeader = <<Left/binary,
|
||||
16#ef, 16#ef, 16#ef, 16#ef,
|
||||
Right/binary>>,
|
||||
new2(DownHeader).
|
||||
|
||||
new2(<<Left:56/binary, _/binary>> = DownHeader) ->
|
||||
Encrypt = init_down_encrypt(DownHeader),
|
||||
Decrypt = init_down_decrypt(DownHeader),
|
||||
St = #st{decrypt = Decrypt,
|
||||
encrypt = Encrypt},
|
||||
{<<_:56/binary, Rep:8/binary, _/binary>>, St1} = encrypt(DownHeader, St),
|
||||
{ok,
|
||||
<<Left/binary, Rep/binary>>,
|
||||
St1}.
|
||||
|
||||
init_down_decrypt(<<_:8/binary, ToRev:48/binary, _/binary>>) ->
|
||||
Reversed = bin_rev(ToRev),
|
||||
<<KeyRev:32/binary, RevIV:16/binary>> = Reversed,
|
||||
?DBG("down-DEC Key: ~w;~nIV: ~w~n", [KeyRev, RevIV]),
|
||||
crypto:stream_init('aes_ctr', KeyRev, RevIV).
|
||||
|
||||
init_down_encrypt(<<_:8/binary, Key:32/binary, IV:16/binary, _/binary>>) ->
|
||||
?DBG("down-ENC Key: ~w;~nIV: ~w~n", [Key, IV]),
|
||||
crypto:stream_init('aes_ctr', Key, IV).
|
||||
|
||||
|
||||
%% @doc creates new obfuscated stream (MTProto proxy format)
|
||||
-spec from_header(binary(), binary()) -> {ok, inet:ip4_address(), codec()}.
|
||||
from_header(Header, Secret) when byte_size(Header) == 64 ->
|
||||
Encrypt = init_up_encrypt(Header, Secret),
|
||||
Decrypt = init_up_decrypt(Header, Secret),
|
||||
{Decrypt1, <<_:56/binary, Bin1:8/binary, _/binary>>} = crypto:stream_encrypt(Decrypt, Header),
|
||||
<<HeaderPart:56/binary, _/binary>> = Header,
|
||||
NewHeader = <<HeaderPart/binary, Bin1/binary>>,
|
||||
case NewHeader of
|
||||
<<_:56/binary, 16#ef, 16#ef, 16#ef, 16#ef, _/binary>> ->
|
||||
Endpoint = get_endpoint(NewHeader),
|
||||
{ok, Endpoint, #st{decrypt = Decrypt1,
|
||||
encrypt = Encrypt}};
|
||||
<<_:56/binary, 16#ee, 16#ee, 16#ee, 16#ee, _/binary>> ->
|
||||
{error, {protocol_not_supported, intermediate}};
|
||||
_ ->
|
||||
{error, unknown_protocol}
|
||||
end.
|
||||
|
||||
init_up_encrypt(Bin, Secret) ->
|
||||
<<_:8/binary, ToRev:48/binary, _/binary>> = Bin,
|
||||
Rev = bin_rev(ToRev),
|
||||
<<KeyRev:32/binary, RevIV:16/binary, _/binary>> = Rev,
|
||||
%% <<_:32/binary, RevIV:16/binary, _/binary>> = Bin,
|
||||
KeyRevHash = crypto:hash('sha256', <<KeyRev/binary, Secret/binary>>),
|
||||
?DBG("up-ENC Key: ~p;~nIV: ~p~n", [KeyRevHash, RevIV]),
|
||||
crypto:stream_init('aes_ctr', KeyRevHash, RevIV).
|
||||
|
||||
init_up_decrypt(Bin, Secret) ->
|
||||
<<_:8/binary, Key:32/binary, _/binary>> = Bin,
|
||||
<<_:40/binary, IV:16/binary, _/binary>> = Bin,
|
||||
KeyHash = crypto:hash('sha256', <<Key/binary, Secret/binary>>),
|
||||
?DBG("up-DEC Key: ~p;~nIV: ~p~n", [KeyHash, IV]),
|
||||
crypto:stream_init('aes_ctr', KeyHash, IV).
|
||||
|
||||
get_endpoint(<<_:60/binary, DcId:16/signed-little-integer, _/binary>>) ->
|
||||
element(abs(DcId), ?ENDPOINTS).
|
||||
|
||||
encrypt(Data, #st{encrypt = Enc} = St) ->
|
||||
{Enc1, Encrypted} = crypto:stream_encrypt(Enc, Data),
|
||||
?DBG("encrypt: IN:~p~nOUT:~p~n", [Data, Encrypted]),
|
||||
{Encrypted, St#st{encrypt = Enc1}}.
|
||||
|
||||
decrypt(Encrypted, #st{decrypt = Dec} = St) ->
|
||||
{Dec1, Data} = crypto:stream_encrypt(Dec, Encrypted),
|
||||
?DBG("decrypt: IN:~p~nOUT:~p~n", [Encrypted, Data]),
|
||||
{Data, St#st{decrypt = Dec1}}.
|
||||
|
||||
|
||||
%% Helpers
|
||||
bin_rev(Bin) ->
|
||||
list_to_binary(lists:reverse(binary_to_list(Bin))).
|
||||
24
src/mtproto_proxy.app.src
Normal file
24
src/mtproto_proxy.app.src
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{application, mtproto_proxy,
|
||||
[{description, "An OTP application"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
{mod, { mtproto_proxy_app, []}},
|
||||
{applications,
|
||||
[lager,
|
||||
ranch,
|
||||
crypto,
|
||||
kernel,
|
||||
stdlib
|
||||
]},
|
||||
{env,[
|
||||
{ip, {0, 0, 0, 0}},
|
||||
{ports, [{mtp_handler, 4430}]},
|
||||
{num_acceptors, 60},
|
||||
{max_connections, 1024},
|
||||
{secret, <<15,61,100,92,77,206,249,22,29,186,62,205,196,61,65,194>>}]},
|
||||
{modules, []},
|
||||
|
||||
{maintainers, []},
|
||||
{licenses, ["Apache 2.0"]},
|
||||
{links, []}
|
||||
]}.
|
||||
51
src/mtproto_proxy_app.erl
Normal file
51
src/mtproto_proxy_app.erl
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% @doc mtproto_proxy public API
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(mtproto_proxy_app).
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
%% Application callbacks
|
||||
-export([start/2, stop/1]).
|
||||
-define(APP, mtproto_proxy).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start(_StartType, _StartArgs) ->
|
||||
Res = mtproto_proxy_sup:start_link(),
|
||||
[start_proxy(Where) || Where <- application:get_env(?APP, ports, [])],
|
||||
Res.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
|
||||
start_proxy({Name, Port}) ->
|
||||
ListenIp = application:get_env(?APP, ip, {0, 0, 0, 0}),
|
||||
NumAcceptors = application:get_env(?APP, num_acceptors, 60),
|
||||
MaxConnections = application:get_env(?APP, max_connections, 1024),
|
||||
Res = {ok, Pid} =
|
||||
ranch:start_listener(
|
||||
Name, ranch_tcp,
|
||||
[{ip, ListenIp},
|
||||
{port, Port},
|
||||
{num_acceptors, NumAcceptors},
|
||||
{max_connections, MaxConnections}],
|
||||
mtp_handler, []),
|
||||
KeyStr = mtp_handler:key_str(),
|
||||
io:format("+++++++++++++++++++++++++++++++++++++++~n"
|
||||
"Erlang MTProto proxy by @seriyps https://github.com/seriyps/mtproto_proxy~n"
|
||||
"Sponsored by and powers @socksy_bot~n"
|
||||
"Proxy started on ~s:~p with key: ~s~n~n"
|
||||
"+++++++++++++++++++++++++++++++++++++++~n",
|
||||
[inet:ntoa(ListenIp), Port, KeyStr]),
|
||||
lager:info("mtproto=~p listening on addr=~s:~p with key ~s",
|
||||
[Pid, inet:ntoa(ListenIp), Port, KeyStr]),
|
||||
Res.
|
||||
40
src/mtproto_proxy_sup.erl
Normal file
40
src/mtproto_proxy_sup.erl
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% @doc mtproto_proxy top level supervisor.
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(mtproto_proxy_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
%%====================================================================
|
||||
%% API functions
|
||||
%%====================================================================
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
%%====================================================================
|
||||
%% Supervisor callbacks
|
||||
%%====================================================================
|
||||
|
||||
%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules}
|
||||
init([]) ->
|
||||
Childs = [
|
||||
],
|
||||
{ok, {#{strategy => rest_for_one,
|
||||
intensity => 50,
|
||||
period => 5},
|
||||
Childs} }.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
Loading…
Add table
Add a link
Reference in a new issue