Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
E
er
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
er
Commits
871ab11a
Unverified
Commit
871ab11a
authored
Mar 01, 2019
by
Сергей Прохоров
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add fake telegram "middle-proxy" server and http config server implementations
And make it possible to make proxy connect to it
parent
aa6e06a4
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
307 additions
and
5 deletions
+307
-5
mtp_config.erl
src/mtp_config.erl
+4
-2
mtproto_proxy.app.src
src/mtproto_proxy.app.src
+7
-2
mtproto_proxy_app.erl
src/mtproto_proxy_app.erl
+1
-1
mtp_test_midle_server.erl
test/mtp_test_midle_server.erl
+295
-0
No files found.
src/mtp_config.erl
View file @
871ab11a
...
...
@@ -168,11 +168,13 @@ update(State, _) ->
end
.
update_key
(
Tab
)
->
{
ok
,
Body
}
=
http_get
(
?
SECRET_URL
),
Url
=
application
:
get_env
(
mtproto_proxy
,
proxy_secret_url
,
?
SECRET_URL
),
{
ok
,
Body
}
=
http_get
(
Url
),
true
=
ets
:
insert
(
Tab
,
{
key
,
list_to_binary
(
Body
)}).
update_config
(
Tab
)
->
{
ok
,
Body
}
=
http_get
(
?
CONFIG_URL
),
Url
=
application
:
get_env
(
mtproto_proxy
,
proxy_config_url
,
?
CONFIG_URL
),
{
ok
,
Body
}
=
http_get
(
Url
),
Downstreams
=
parse_config
(
Body
),
update_downstreams
(
Downstreams
,
Tab
),
update_ids
(
Downstreams
,
Tab
).
...
...
src/mtproto_proxy.app.src
View file @
871ab11a
...
...
@@ -63,12 +63,17 @@
%% Should be module with function `notify/4' exported.
%% See mtp_metric:notify/4 for details
%% {metric_backend, my_metric_backend}
%% {metric_backend, my_metric_backend}
,
%% User-space recv socket buffer sizes. Set to higher if you have
%% enough RAM
%% {upstream_socket_buffer_size, 51200}, %50kb
%% {downstream_socket_buffer_size, 512000} %500kb
%% {downstream_socket_buffer_size, 512000}, %500kb
%% Where to fetch telegram proxy configuration
%% Mostly used to testing
%% {proxy_secret_url, "https://core.telegram.org/getProxySecret"},
%% {proxy_config_url, "https://core.telegram.org/getProxyConfig"},
]},
{modules, []},
...
...
src/mtproto_proxy_app.erl
View file @
871ab11a
...
...
@@ -15,7 +15,7 @@
%% API
%%====================================================================
start
(_
StartType
,
_
StartArgs
)
->
Res
=
mtproto_proxy_sup
:
start_link
(),
Res
=
{
ok
,
_}
=
mtproto_proxy_sup
:
start_link
(),
io
:
format
(
"+++++++++++++++++++++++++++++++++++++++
~n
"
"Erlang MTProto proxy by @seriyps https://github.com/seriyps/mtproto_proxy
~n
"
"Sponsored by and powers @socksy_bot
~n
"
),
...
...
test/mtp_test_midle_server.erl
0 → 100644
View file @
871ab11a
%% Fake telegram server
%% Secret = crypto:strong_rand_bytes(128).
%% DcConf = [{1, {127, 0, 0, 1}, 8888}, {2, {127, 0, 0, 1}, 8889}].
%% Cfg = mtp_test_midle_server:dc_list_to_config(DcConf).
%% mtp_test_midle_server:start_config_server({127, 0, 0, 1}, 3333, Secret, Cfg).
%% mtp_test_midle_server:start(dc1, #{port => 8888, ip => {127, 0, 0, 1}, secret => Secret}).
%% mtp_test_midle_server:start(dc2, #{port => 8889, ip => {127, 0, 0, 1}, secret => Secret}).
%% application:ensure_all_started(mtproto_proxy).
-
module
(
mtp_test_midle_server
).
-
behaviour
(
ranch_protocol
).
-
behaviour
(
gen_statem
).
-
export
([
start_dc
/
0
,
start_dc
/
3
,
stop_dc
/
1
,
start
/
2
,
stop
/
1
,
start_config_server
/
5
,
stop_config_server
/
1
]).
-
export
([
dc_list_to_config
/
1
]).
-
export
([
start_link
/
4
,
ranch_init
/
1
]).
-
export
([
do
/
1
]).
-
export
([
init
/
1
,
callback_mode
/
0
,
%% handle_call/3,
%% handle_cast/2,
%% handle_info/2,
code_change
/
3
,
terminate
/
2
]).
-
export
([
wait_nonce
/
3
,
wait_handshake
/
3
,
on_tunnel
/
3
]).
-
include_lib
(
"inets/include/httpd.hrl"
).
-
record
(
hs_state
,
{
sock
,
transport
,
secret
,
codec
::
mtp_codec
:
codec
(),
cli_nonce
,
cli_ts
,
sender_pid
,
peer_pid
,
srv_nonce
}).
-
record
(
t_state
,
{
sock
,
transport
,
codec
,
clients
=
#
{}
::
#
{}}).
-
define
(
RPC_NONCE
,
170
,
135
,
203
,
122
).
-
define
(
RPC_HANDSHAKE
,
245
,
238
,
130
,
118
).
-
define
(
RPC_FLAGS
,
0
,
0
,
0
,
0
).
-
define
(
SECRET_PATH
,
"/getProxySecret"
).
-
define
(
CONFIG_PATH
,
"/getProxyConfig"
).
-
type
state_name
()
::
wait_nonce
|
wait_handshake
|
on_tunnel
.
start_dc
()
->
Secret
=
crypto
:
strong_rand_bytes
(
128
),
DcConf
=
[{
1
,
{
127
,
0
,
0
,
1
},
8888
}],
{
ok
,
_
Cfg
}
=
mtp_test_midle_server
:
start_dc
(
Secret
,
DcConf
,
#
{}).
start_dc
(
Secret
,
DcConf
,
Acc
)
->
Cfg
=
mtp_test_midle_server
:
dc_list_to_config
(
DcConf
),
{
ok
,
Acc1
}
=
start_config_server
({
127
,
0
,
0
,
1
},
3333
,
Secret
,
Cfg
,
Acc
),
Ids
=
[
begin
Id
=
{
?
MODULE
,
DcId
},
{
ok
,
_
Pid
}
=
start
(
Id
,
#
{
port
=>
Port
,
ip
=>
Ip
,
secret
=>
Secret
}),
Id
end
||
{
DcId
,
Ip
,
Port
}
<-
DcConf
],
{
ok
,
Acc1
#
{
srv_ids
=>
Ids
}}.
stop_dc
(
#
{
srv_ids
:
=
Ids
}
=
Acc
)
->
Acc1
=
stop_config_server
(
Acc
),
ok
=
lists
:
foreach
(
fun
stop
/
1
,
Ids
),
{
ok
,
maps
:
without
([
srv_ids
],
Acc1
)}.
%%
%% Inets HTTPD to use as a mock for https://core.telegram.org
%%
%% Api
start_config_server
(
Ip
,
Port
,
Secret
,
DcConfig
,
Acc
)
->
Netloc
=
lists
:
flatten
(
io_lib
:
format
(
"http://
~s
:
~w
"
,
[
inet
:
ntoa
(
Ip
),
Port
])),
Env
=
[{
proxy_secret_url
,
Netloc
++
?
SECRET_PATH
},
{
proxy_config_url
,
Netloc
++
?
CONFIG_PATH
},
{
external_ip
,
"127.0.0.1"
},
{
init_dc_connections
,
1
},
{
num_acceptors
,
4
},
{
ip_lookup_services
,
undefined
}],
OldEnv
=
[
begin
%% OldV is undefined | {ok, V}
OldV
=
application
:
get_env
(
mtproto_proxy
,
K
),
case
V
of
undefined
->
application
:
unset_env
(
mtproto_proxy
,
K
);
_
->
application
:
set_env
(
mtproto_proxy
,
K
,
V
)
end
,
{
K
,
OldV
}
end
||
{
K
,
V
}
<-
Env
],
{
ok
,
Pid
}
=
inets
:
start
(
httpd
,
[{
port
,
Port
},
{
server_name
,
"mtp_config"
},
{
server_root
,
"/tmp"
},
{
document_root
,
code
:
priv_dir
(
mtproto_proxy
)},
{
bind_address
,
Ip
},
{
modules
,
[
?
MODULE
]},
{
mtp_secret
,
Secret
},
{
mtp_dc_conf
,
DcConfig
}]),
{
ok
,
Acc
#
{
env
=>
OldEnv
,
httpd_pid
=>
Pid
}}.
stop_config_server
(
#
{
env
:
=
Env
,
httpd_pid
:
=
Pid
}
=
Acc
)
->
[
case
V
of
undefined
->
application
:
unset_env
(
mtproto_proxy
,
K
);
{
ok
,
Val
}
->
application
:
set_env
(
mtproto_proxy
,
K
,
Val
)
end
||
{
K
,
V
}
<-
Env
],
inets
:
stop
(
httpd
,
Pid
),
{
ok
,
maps
:
without
([
env
,
httpd_pid
],
Acc
)}.
dc_list_to_config
(
List
)
->
<<
<<
(
list_to_binary
(
io_lib
:
format
(
"proxy_for
~w
~s
:
~w
;
~n
"
,
[
DcId
,
inet
:
ntoa
(
Ip
),
Port
])
))
/
binary
>>
||
{
DcId
,
Ip
,
Port
}
<-
List
>>
.
%% Inets callback
do
(
#mod
{
request_uri
=
?
CONFIG_PATH
,
config_db
=
Db
})
->
[{_,
DcConf
}]
=
ets
:
lookup
(
Db
,
mtp_dc_conf
),
{
break
,
[{
response
,
{
200
,
binary_to_list
(
DcConf
)}}]};
do
(
#mod
{
request_uri
=
?
SECRET_PATH
,
config_db
=
Db
})
->
[{_,
Secret
}]
=
ets
:
lookup
(
Db
,
mtp_secret
),
{
break
,
[{
response
,
{
200
,
binary_to_list
(
Secret
)}}]}.
%%
%% Mtproto telegram server
%%
%% Api
start
(
Id
,
Opts
)
->
{
ok
,
_}
=
application
:
ensure_all_started
(
ranch
),
ranch
:
start_listener
(
Id
,
ranch_tcp
,
#
{
socket_opts
=>
[{
ip
,
{
127
,
0
,
0
,
1
}},
{
port
,
maps
:
get
(
port
,
Opts
)}],
num_acceptors
=>
2
,
max_connections
=>
100
},
?
MODULE
,
Opts
).
stop
(
Id
)
->
ranch
:
stop_listener
(
Id
).
%% Callbacks
start_link
(
Ref
,
_,
Transport
,
Opts
)
->
{
ok
,
proc_lib
:
spawn_link
(
?
MODULE
,
ranch_init
,
[{
Ref
,
Transport
,
Opts
}])}.
ranch_init
({
Ref
,
Transport
,
Opts
})
->
{
ok
,
Socket
}
=
ranch
:
handshake
(
Ref
),
{
ok
,
StateName
,
StateData
}
=
init
({
Socket
,
Transport
,
Opts
}),
ok
=
Transport
:
setopts
(
Socket
,
[{
active
,
once
}]),
gen_statem
:
enter_loop
(
?
MODULE
,
[],
StateName
,
StateData
).
init
({
Socket
,
Transport
,
Opts
})
->
Codec
=
mtp_codec
:
new
(
mtp_noop_codec
,
mtp_noop_codec
:
new
(),
mtp_full
,
mtp_full
:
new
(
-
2
,
-
2
)),
{
ok
,
wait_nonce
,
#hs_state
{
sock
=
Socket
,
transport
=
Transport
,
secret
=
maps
:
get
(
secret
,
Opts
),
codec
=
Codec
}}.
callback_mode
()
->
state_functions
.
terminate
(_
Reason
,
_
State
)
->
ok
.
code_change
(_
OldVsn
,
State
,
_
Extra
)
->
{
ok
,
State
}.
%%
%% State handlers
%%
wait_nonce
(
info
,
{
tcp
,
_
Sock
,
TcpData
},
#hs_state
{
codec
=
Codec0
,
secret
=
Key
,
transport
=
Transport
,
sock
=
Sock
}
=
S
)
->
%% Hope whole protocol packet fit in 1 TCP packet
{
ok
,
PacketData
,
Codec1
}
=
mtp_codec
:
try_decode_packet
(
TcpData
,
Codec0
),
<<
KeySelector
:
4
/
binary
,
_
/
binary
>>
=
Key
,
{
nonce
,
KeySelector
,
Schema
,
CryptoTs
,
CliNonce
}
=
mtp_rpc
:
decode_nonce
(
PacketData
),
SrvNonce
=
crypto
:
strong_rand_bytes
(
16
),
Answer
=
mtp_rpc
:
encode_nonce
({
nonce
,
KeySelector
,
Schema
,
CryptoTs
,
SrvNonce
}),
%% Send non-encrypted nonce
{
ok
,
#hs_state
{
codec
=
Codec2
}
=
S1
}
=
hs_send
(
Answer
,
S
#hs_state
{
codec
=
Codec1
}),
%% Generate keys
{
ok
,
{
CliIp
,
CliPort
}}
=
Transport
:
peername
(
Sock
),
{
ok
,
{
MyIp
,
MyPort
}}
=
Transport
:
sockname
(
Sock
),
CliIpBin
=
mtp_obfuscated
:
bin_rev
(
mtp_rpc
:
inet_pton
(
CliIp
)),
MyIpBin
=
mtp_obfuscated
:
bin_rev
(
mtp_rpc
:
inet_pton
(
MyIp
)),
Args
=
#
{
srv_n
=>
SrvNonce
,
clt_n
=>
CliNonce
,
clt_ts
=>
CryptoTs
,
srv_ip
=>
MyIpBin
,
srv_port
=>
MyPort
,
clt_ip
=>
CliIpBin
,
clt_port
=>
CliPort
,
secret
=>
Key
},
{
DecKey
,
DecIv
}
=
mtp_down_conn
:
get_middle_key
(
Args
#
{
purpose
=>
<<
"CLIENT"
>>
}),
{
EncKey
,
EncIv
}
=
mtp_down_conn
:
get_middle_key
(
Args
#
{
purpose
=>
<<
"SERVER"
>>
}),
%% Add encryption layer to codec
{_,
_,
PacketMod
,
PacketState
}
=
mtp_codec
:
decompose
(
Codec2
),
CryptoState
=
mtp_aes_cbc
:
new
(
EncKey
,
EncIv
,
DecKey
,
DecIv
,
16
),
Codec3
=
mtp_codec
:
new
(
mtp_aes_cbc
,
CryptoState
,
PacketMod
,
PacketState
),
{
next_state
,
wait_handshake
,
activate
(
S1
#hs_state
{
codec
=
Codec3
,
cli_nonce
=
CliNonce
,
cli_ts
=
CryptoTs
,
srv_nonce
=
SrvNonce
})};
wait_nonce
(
Type
,
Event
,
S
)
->
handle_event
(
Type
,
Event
,
?
FUNCTION_NAME
,
S
).
wait_handshake
(
info
,
{
tcp
,
_
Sock
,
TcpData
},
#hs_state
{
codec
=
Codec0
}
=
S
)
->
{
ok
,
PacketData
,
Codec1
}
=
mtp_codec
:
try_decode_packet
(
TcpData
,
Codec0
),
{
handshake
,
SenderPID
,
PeerPID
}
=
mtp_rpc
:
decode_handshake
(
PacketData
),
Answer
=
mtp_rpc
:
encode_handshake
({
handshake
,
SenderPID
,
PeerPID
}),
{
ok
,
#hs_state
{
sock
=
Sock
,
transport
=
Transport
,
codec
=
Codec2
}}
=
hs_send
(
Answer
,
S
#hs_state
{
codec
=
Codec1
}),
{
next_state
,
on_tunnel
,
activate
(
#t_state
{
sock
=
Sock
,
transport
=
Transport
,
codec
=
Codec2
,
clients
=
#
{}})}.
on_tunnel
(
info
,
{
tcp
,
_
Sock
,
TcpData
},
#t_state
{
codec
=
Codec0
}
=
S
)
->
{
ok
,
S2
,
Codec1
}
=
mtp_codec
:
fold_packets
(
fun
(
Packet
,
S1
)
->
handle_rpc
(
mtp_rpc
:
srv_decode_packet
(
Packet
),
S1
)
end
,
S
,
TcpData
,
Codec0
),
{
keep_state
,
activate
(
S2
#t_state
{
codec
=
Codec1
})}.
handle_event
(
info
,
{
tcp_closed
,
_
Sock
},
_
EventName
,
_
S
)
->
{
stop
,
normal
}.
%% Helpers
hs_send
(
Packet
,
#hs_state
{
transport
=
Transport
,
sock
=
Sock
,
codec
=
Codec
}
=
St
)
->
%% lager:debug("Up>Down: ~w", [Packet]),
{
Encoded
,
Codec1
}
=
mtp_codec
:
encode_packet
(
Packet
,
Codec
),
ok
=
Transport
:
send
(
Sock
,
Encoded
),
{
ok
,
St
#hs_state
{
codec
=
Codec1
}}.
t_send
(
Packet
,
#t_state
{
transport
=
Transport
,
sock
=
Sock
,
codec
=
Codec
}
=
St
)
->
%% lager:debug("Up>Down: ~w", [Packet]),
{
Encoded
,
Codec1
}
=
mtp_codec
:
encode_packet
(
Packet
,
Codec
),
ok
=
Transport
:
send
(
Sock
,
Encoded
),
{
ok
,
St
#t_state
{
codec
=
Codec1
}}.
activate
(
#hs_state
{
transport
=
Transport
,
sock
=
Sock
}
=
S
)
->
ok
=
Transport
:
setopts
(
Sock
,
[{
active
,
once
}]),
S
;
activate
(
#t_state
{
transport
=
Transport
,
sock
=
Sock
}
=
S
)
->
ok
=
Transport
:
setopts
(
Sock
,
[{
active
,
once
}]),
S
.
handle_rpc
({
data
,
ConnId
,
Data
},
#t_state
{
clients
=
Clients
}
=
S
)
->
%% Echo data back
%% TODO: interptet Data to power some test scenarios, eg, client might
%% ask to close it's connection
{
ok
,
S1
}
=
t_send
(
mtp_rpc
:
srv_encode_packet
({
proxy_ans
,
ConnId
,
Data
}),
S
),
Cnt
=
maps
:
get
(
ConnId
,
Clients
,
0
),
%% Increment can fail if there is a tombstone for this client
S1
#t_state
{
clients
=
Clients
#
{
ConnId
=>
Cnt
+
1
}};
handle_rpc
({
remote_closed
,
ConnId
},
#t_state
{
clients
=
Clients
}
=
S
)
->
is_integer
(
maps
:
get
(
ConnId
,
Clients
))
orelse
error
({
unexpected_closed
,
ConnId
}),
S
#t_state
{
clients
=
Clients
#
{
ConnId
:
=
tombstone
}}.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment