Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
mtproto_proxy
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
mtproto_proxy
Commits
5c8cc0c5
Unverified
Commit
5c8cc0c5
authored
Aug 20, 2019
by
Sergey Prokhorov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Experimental support for connection policies
parent
dcdf05a3
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
464 additions
and
45 deletions
+464
-45
README.md
README.md
+109
-12
mtp_fake_tls.erl
src/mtp_fake_tls.erl
+11
-5
mtp_handler.erl
src/mtp_handler.erl
+24
-15
mtp_policy.erl
src/mtp_policy.erl
+132
-0
mtp_policy_counter.erl
src/mtp_policy_counter.erl
+80
-0
mtp_policy_table.erl
src/mtp_policy_table.erl
+94
-0
mtproto_proxy.app.src
src/mtproto_proxy.app.src
+7
-5
mtproto_proxy_app.erl
src/mtproto_proxy_app.erl
+2
-7
mtproto_proxy_sup.erl
src/mtproto_proxy_sup.erl
+5
-1
No files found.
README.md
View file @
5c8cc0c5
...
@@ -6,19 +6,22 @@ This part of code was extracted from [@socksy_bot](https://t.me/socksy_bot).
...
@@ -6,19 +6,22 @@ This part of code was extracted from [@socksy_bot](https://t.me/socksy_bot).
Features
Features
--------
--------
*
Promoted channels
! See
`mtproto_proxy_app.src`
`tag`
option.
*
Promoted channels
. See
`tag`
option.
*
"secure" randomized-packet-size protocol (34-symbol secrets starting with 'dd')
*
"secure" randomized-packet-size protocol (34-symbol secrets starting with 'dd')
to prevent detection by DPI
to prevent detection by DPI
*
Fake-TLS protocol (base64 secrets) - another protocol to prevent DPI detection
*
Fake-TLS protocol (
'ee'/
base64 secrets) - another protocol to prevent DPI detection
*
Secure-only mode (only allow connections with 'dd' or fake-
tls-base64-secrets
).
*
Secure-only mode (only allow connections with 'dd' or fake-
TLS
).
See
`allowed_protocols`
option.
See
`allowed_protocols`
option.
*
Connection limit policies - limit number of connections by IP / tls-domain / port; IP / tls-domain
blacklists / whitelists
*
Multiple ports with unique secret and promo tag for each port
*
Multiple ports with unique secret and promo tag for each port
*
Very high performance - can handle tens of thousands connections! Scales to all CPU cores.
*
Very high performance - can handle tens of thousands connections! Scales to all CPU cores.
1Gbps, 90k connections on 4-core/8Gb RAM cloud server.
1Gbps, 90k connections on 4-core/8Gb RAM cloud server.
*
Supports multiplexing (Many connections Client -> Proxy are wrapped to small amount of
*
Supports multiplexing (Many connections Client -> Proxy are wrapped to small amount of
connections Proxy -> Telegram Server)
connections Proxy -> Telegram Server)
- lower pings and better OS network utilization
*
Protection from
[
replay attacks
](
https://habr.com/ru/post/452144/
)
used to detect proxies in some countries
*
Protection from
[
replay attacks
](
https://habr.com/ru/post/452144/
)
used to detect proxies in some countries
*
Automatic telegram configuration reload (no need for restarts once per day)
*
Automatic telegram configuration reload (no need for restarts once per day)
*
IPv6 for client connections
*
Most of the configuration options can be updated without service restart
*
Most of the configuration options can be updated without service restart
*
Small codebase compared to official one, code is covered by automated tests
*
Small codebase compared to official one, code is covered by automated tests
*
A lots of metrics could be exported (optional)
*
A lots of metrics could be exported (optional)
...
@@ -27,20 +30,20 @@ How to install - one-line interactive installer
...
@@ -27,20 +30,20 @@ How to install - one-line interactive installer
-----------------------------------------------
-----------------------------------------------
This command will run
[
interactive script
](
https://gist.github.com/seriyps/dc00ad91bfd8a2058f30845cd0daed83
)
This command will run
[
interactive script
](
https://gist.github.com/seriyps/dc00ad91bfd8a2058f30845cd0daed83
)
that will install and configure proxy for your Ubuntu
18.04
server.
that will install and configure proxy for your Ubuntu
/ Debian / CentOS
server.
It will ask if you want to change default port/secret/ad-tag/protocols:
It will ask if you want to change default port/secret/ad-tag/protocols:
```
bash
```
bash
curl
-L
-o
mtp_install.sh https://git.io/fj5ru
&&
bash mtp_install.sh
curl
-L
-o
mtp_install.sh https://git.io/fj5ru
&&
bash mtp_install.sh
```
```
You can also just provide port/secret/ad-tag/protocols as command line arguments:
You can also just provide port/secret/ad-tag/protocols
/tls-domain
as command line arguments:
```
bash
```
bash
curl
-L
-o
mtp_install.sh https://git.io/fj5ru
&&
bash mtp_install.sh
-p
443
-s
d0d6e111bada5511fcce9584deadbeef
-t
dcbe8f1493fa4cd9ab300891c0b5b326
-a
dd
-a
tls
curl
-L
-o
mtp_install.sh https://git.io/fj5ru
&&
bash mtp_install.sh
-p
443
-s
d0d6e111bada5511fcce9584deadbeef
-t
dcbe8f1493fa4cd9ab300891c0b5b326
-a
dd
-a
tls
-d
s3.amazonaws.com
```
```
It does the same as described in
[
How to start OS-install - detailed
](
#how-to-start-os-install---detailed
)
but
It does the same as described in
[
How to start OS-install - detailed
](
#how-to-start-os-install---detailed
)
,
but
generates config-file for you automatically.
generates config-file for you automatically.
How to start - docker
How to start - docker
...
@@ -108,10 +111,24 @@ How to start OS-install - detailed
...
@@ -108,10 +111,24 @@ How to start OS-install - detailed
--------------------------------------
--------------------------------------
### Install deps (ubuntu 18.04)
### Install deps
Ubuntu 18.xx / Ubuntu 19.xx / Debian 10:
```
bash
```
bash
sudo
apt
install
erlang-nox erlang-dev build-essential
sudo
apt
install
erlang-nox erlang-dev make
sed
diffutils
tar
```
CentOS 7
```
bash
# Enable "epel" and "Erlang solutions" repositories
sudo
yum
install
wget
\
https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
\
https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
# Install Erlang
sudo
yum
install
erlang-compiler erlang-erts erlang-kernel erlang-stdlib erlang-syntax_tools
\
erlang-crypto erlang-inets erlang-sasl erlang-ssl
```
```
You need Erlang version 20 or higher! If your version is older, please, check
You need Erlang version 20 or higher! If your version is older, please, check
...
@@ -273,12 +290,12 @@ You should disable all protocols other than `mtp_secure` by providing `allowed_p
...
@@ -273,12 +290,12 @@ You should disable all protocols other than `mtp_secure` by providing `allowed_p
<
..
>
<
..
>
```
```
### Only allow fake-TLS connections with base64-secrets
### Only allow fake-TLS connections with
ee/
base64-secrets
Another censorship circumvention technique. MTPRoto proxy protocol pretends to be
Another censorship circumvention technique. MTPRoto proxy protocol pretends to be
HTTPS web traffic (technically speaking, TLSv1.3 + HTTP/2).
HTTPS web traffic (technically speaking, TLSv1.3 + HTTP/2).
It's possible to only allow connections with this protocol by changing
`allowed_protocols`
to
It's possible to only allow connections with this protocol by changing
`allowed_protocols`
to
be list with only
`mtp_fake_tls`
.
You may also want to check
`tls_allowed_domains`
option.
be list with only
`mtp_fake_tls`
.
```
erlang
```
erlang
{
mtproto_proxy
,
{
mtproto_proxy
,
...
@@ -289,6 +306,85 @@ be list with only `mtp_fake_tls`. You may also want to check `tls_allowed_domain
...
@@ -289,6 +306,85 @@ be list with only `mtp_fake_tls`. You may also want to check `tls_allowed_domain
<
..
>
<
..
>
```
```
### Connection limit policies
Proxy supports flexible connection limit rules. It's possible to limit number of connections from
single IP or to single fake-TLS domain or to single port name; or any combination of them.
It also supports whitelists and blacklists: you can allow or forbid to connect from some IP or IP subnet
or with some TLS domains.
Policy is set as value of
`policy`
config key and the value is the list of policy structures.
If list is empty, no limits will be checked.
Following policies are supported:
*
`{max_connections, KEYS, NUMBER}`
- EXPERIMENTAL! if there are more than NUMBER connections with
KEYS to the proxy, new connections with those KEYS will be rejected
*
`{in_table, KEY, TABLE_NAME}`
- only allow connections if KEY is present in TABLE_NAME (whitelist)
*
`{not_in_table, KEY, TABLE_NAME}`
- only allow connections if KEY is
*not*
present in TABLE_NAME (blacklist)
Where:
-
`KEY`
is one of:
-
`port_name`
- proxy port name
-
`client_ipv4`
- client's IPv4 address; ignored on IPv6 ports!
-
`client_ipv6`
- client's IPv6 address; ignored on IPv4 ports!
-
`{client_ipv4_subnet, MASK}`
- client's IPv4 subnet; mask is from 8 to 32
-
`{client_ipv6_subnet, MASK}`
- client's IPv6 subnet; mask is from 32 to 128
-
`tls_domain`
- domain name from fake-TLS secret; ignored if connection with non-fake-TLS protocol
-
`KEYS`
is a list of one or more
`KEY`
, eg,
`[port, tls_domain]`
-
`TABLE_NAME`
is free-form text name of special internal database table, eg,
`my_table`
.
Tables will be created automatically when proxy is started; data in tables is not preserved when proxy
is restarted!
You can add or remove new values from table dynamically at any moment with commands like:
-
`/opt/mtp_proxy/bin/mtp_proxy eval 'mtp_policy_table:add(my_table, tls_domain, "google.com")'`
to add
-
`/opt/mtp_proxy/bin/mtp_proxy eval 'mtp_policy_table:del(my_table, tls_domain, "google.com")'`
to remove
Some policy recipes / examples below
#### Limit max connections to proxy port from single IP
Here we allow maximum 100 concurrent connections from single IP to proxy port:
```
erlang
{
mtproto_proxy
,
[
{
policy
,
[{
max_connections
,
[
port_name
,
client_ipv4
],
100
}]},
{
ports
,
<
..
>
```
#### Limit number of connections with single fake-TLS domain; only allow certain domains
We basically can assign each customer unique fake-TLS domain, give them unique TLS secret
and allow only 10 connections with this fake-TLS secret, so, they will not shere their credentials with
others:
```
erlang
{
mtproto_proxy
[
{
policy
,
[{
max_connections
,
[
port_name
,
tls_domain
],
15
},
{
in_table
,
tls_domain
,
customer_domains
}]},
{
ports
,
<
..
>
```
After that we can create unique fake-TLS secret for each customer using code like this:
```
erlang
/
opt
/
mtp_proxy
/
bin
/
mtp_proxy
eval
'
ProxySecret
=
mtp_handler
:
unhex
(
maps
:
get
(
secret
,
hd
(
application
:
get_env
(
mtproto_proxy
,
ports
,
[])))),
NumRecords
=
mtp_policy_table
:
table_size
(
customer_domains
),
Rand
=
crypto
:
rand_bytes
(
2
),
SubDomain
=
mtp_handler
:
hex
(
<<
NumRecords
:
16
,
Rand
/
binary
>>
),
Domain
=
<<
SubDomain
/
binary
,
".google.com"
>>
,
mtp_policy_table
:
add
(
customer_domains
,
tls_domain
,
Domain
),
Secret
=
mtp_handler
:
hex
(
<<
16#ee
,
ProxySecret
/
binary
,
Domain
/
binary
>>
),
io
:
format
(
"Secret:
~s
;
\n
Domain:
~s
\n
"
,
[
Secret
,
Domain
]).
'
```
### IPv6
### IPv6
Currently proxy only supports client connections via IPv6, but can only connect to Telegram servers
Currently proxy only supports client connections via IPv6, but can only connect to Telegram servers
...
@@ -333,6 +429,7 @@ If your server have low amount of RAM, try to set
...
@@ -333,6 +429,7 @@ If your server have low amount of RAM, try to set
this may make proxy slower, it can start to consume bit more CPU, will be vulnerable to replay attacks,
this may make proxy slower, it can start to consume bit more CPU, will be vulnerable to replay attacks,
but will use less RAM.
but will use less RAM.
You should also avoid
`max_connections`
policy because it uses RAM to track connections.
If your server have lots of RAM, you can make it faster (users will get higher uppload/download speed),
If your server have lots of RAM, you can make it faster (users will get higher uppload/download speed),
it will use less CPU and will be better protected from replay attacks, but will use more RAM:
it will use less CPU and will be better protected from replay attacks, but will use more RAM:
...
...
src/mtp_fake_tls.erl
View file @
5c8cc0c5
...
@@ -11,7 +11,8 @@
...
@@ -11,7 +11,8 @@
-
behaviour
(
mtp_codec
).
-
behaviour
(
mtp_codec
).
-
export
([
format_secret
/
2
]).
-
export
([
format_secret_base64
/
2
,
format_secret_hex
/
2
]).
-
export
([
from_client_hello
/
2
,
-
export
([
from_client_hello
/
2
,
new
/
0
,
new
/
0
,
try_decode_packet
/
2
,
try_decode_packet
/
2
,
...
@@ -79,11 +80,16 @@
...
@@ -79,11 +80,16 @@
%% @doc format TLS secret
%% @doc format TLS secret
-
spec
format_secret
(
binary
(),
binary
())
->
binary
().
format_secret_hex
(
Secret
,
Domain
)
when
byte_size
(
Secret
)
==
16
->
format_secret
(
Secret
,
Domain
)
when
byte_size
(
Secret
)
==
16
->
mtp_handler
:
hex
(
<<
16#ee
,
Secret
/
binary
,
Domain
/
binary
>>
);
format_secret_hex
(
HexSecret
,
Domain
)
when
byte_size
(
HexSecret
)
==
32
->
format_secret_hex
(
mtp_handler
:
unhex
(
HexSecret
),
Domain
).
-
spec
format_secret_base64
(
binary
(),
binary
())
->
binary
().
format_secret_base64
(
Secret
,
Domain
)
when
byte_size
(
Secret
)
==
16
->
base64url
(
<<
16#ee
,
Secret
/
binary
,
Domain
/
binary
>>
);
base64url
(
<<
16#ee
,
Secret
/
binary
,
Domain
/
binary
>>
);
format_secret
(
HexSecret
,
Domain
)
when
byte_size
(
HexSecret
)
==
32
->
format_secret
_base64
(
HexSecret
,
Domain
)
when
byte_size
(
HexSecret
)
==
32
->
format_secret
(
mtp_handler
:
unhex
(
HexSecret
),
Domain
).
format_secret
_base64
(
mtp_handler
:
unhex
(
HexSecret
),
Domain
).
base64url
(
Bin
)
->
base64url
(
Bin
)
->
%% see https://hex.pm/packages/base64url
%% see https://hex.pm/packages/base64url
...
...
src/mtp_handler.erl
View file @
5c8cc0c5
...
@@ -50,6 +50,7 @@
...
@@ -50,6 +50,7 @@
ad_tag
::
binary
(),
ad_tag
::
binary
(),
addr
::
mtp_config
:
netloc_v4v6
(),
% IP/Port of remote side
addr
::
mtp_config
:
netloc_v4v6
(),
% IP/Port of remote side
policy_state
::
any
(),
started_at
::
pos_integer
(),
started_at
::
pos_integer
(),
timer_state
=
init
::
init
|
hibernate
|
stop
,
timer_state
=
init
::
init
|
hibernate
|
stop
,
timer
::
gen_timeout
:
tout
(),
timer
::
gen_timeout
:
tout
(),
...
@@ -212,7 +213,14 @@ handle_info(Other, S) ->
...
@@ -212,7 +213,14 @@ handle_info(Other, S) ->
?
log
(
warning
,
"Unexpected msg
~p
"
,
[
Other
]),
?
log
(
warning
,
"Unexpected msg
~p
"
,
[
Other
]),
{
noreply
,
S
}.
{
noreply
,
S
}.
terminate
(_
Reason
,
#state
{
started_at
=
Started
,
listener
=
Listener
}
=
S
)
->
terminate
(_
Reason
,
#state
{
started_at
=
Started
,
listener
=
Listener
,
addr
=
{
Ip
,
_},
policy_state
=
MaybeSni
}
=
S
)
->
try
mtp_policy
:
dec
(
application
:
get_env
(
?
APP
,
policy
,
[]),
Listener
,
Ip
,
MaybeSni
)
catch
T
:
R
->
?
log
(
warning
,
"Failed to decrement policy:
~p
:
~p
"
,
[
T
,
R
])
end
,
maybe_close_down
(
S
),
maybe_close_down
(
S
),
mtp_metric
:
count_inc
([
?
APP
,
in_connection_closed
,
total
],
1
,
#
{
labels
=>
[
Listener
]}),
mtp_metric
:
count_inc
([
?
APP
,
in_connection_closed
,
total
],
1
,
#
{
labels
=>
[
Listener
]}),
Lifetime
=
erlang
:
system_time
(
millisecond
)
-
Started
,
Lifetime
=
erlang
:
system_time
(
millisecond
)
-
Started
,
...
@@ -295,16 +303,17 @@ parse_upstream_data(<<?TLS_START, _/binary>> = AllData,
...
@@ -295,16 +303,17 @@ parse_upstream_data(<<?TLS_START, _/binary>> = AllData,
assert_protocol
(
mtp_fake_tls
),
assert_protocol
(
mtp_fake_tls
),
<<
Data
:(
?
TLS_CLIENT_HELLO_LEN
+
5
)
/
binary
,
Tail
/
binary
>>
=
AllData
,
<<
Data
:(
?
TLS_CLIENT_HELLO_LEN
+
5
)
/
binary
,
Tail
/
binary
>>
=
AllData
,
{
ok
,
Response
,
Meta
,
TlsCodec
}
=
mtp_fake_tls
:
from_client_hello
(
Data
,
Secret
),
{
ok
,
Response
,
Meta
,
TlsCodec
}
=
mtp_fake_tls
:
from_client_hello
(
Data
,
Secret
),
check_tls_
access
(
Listener
,
Ip
,
Meta
),
check_tls_
policy
(
Listener
,
Ip
,
Meta
),
Codec1
=
mtp_codec
:
replace
(
tls
,
true
,
TlsCodec
,
Codec0
),
Codec1
=
mtp_codec
:
replace
(
tls
,
true
,
TlsCodec
,
Codec0
),
Codec
=
mtp_codec
:
push_back
(
tls
,
Tail
,
Codec1
),
Codec
=
mtp_codec
:
push_back
(
tls
,
Tail
,
Codec1
),
ok
=
up_send_raw
(
Response
,
S
),
ok
=
up_send_raw
(
Response
,
S
),
{
ok
,
S
#state
{
codec
=
Codec
,
stage
=
init
}};
{
ok
,
S
#state
{
codec
=
Codec
,
stage
=
init
,
policy_state
=
maps
:
get
(
sni_domain
,
Meta
,
undefined
)}};
parse_upstream_data
(
<<?
TLS_START
,
_
/
binary
>>
=
Data
,
#state
{
stage
=
init
}
=
S
)
->
parse_upstream_data
(
<<?
TLS_START
,
_
/
binary
>>
=
Data
,
#state
{
stage
=
init
}
=
S
)
->
parse_upstream_data
(
Data
,
S
#state
{
stage
=
tls_hello
});
parse_upstream_data
(
Data
,
S
#state
{
stage
=
tls_hello
});
parse_upstream_data
(
<<
Header
:
64
/
binary
,
Rest
/
binary
>>
,
parse_upstream_data
(
<<
Header
:
64
/
binary
,
Rest
/
binary
>>
,
#state
{
stage
=
init
,
secret
=
Secret
,
listener
=
Listener
,
codec
=
Codec0
,
#state
{
stage
=
init
,
secret
=
Secret
,
listener
=
Listener
,
codec
=
Codec0
,
ad_tag
=
Tag
,
addr
=
Addr
}
=
S
)
->
ad_tag
=
Tag
,
addr
=
{
Ip
,
_}
=
Addr
}
=
S
)
->
case
mtp_obfuscated
:
from_header
(
Header
,
Secret
)
of
case
mtp_obfuscated
:
from_header
(
Header
,
Secret
)
of
{
ok
,
DcId
,
PacketLayerMod
,
CryptoCodecSt
}
->
{
ok
,
DcId
,
PacketLayerMod
,
CryptoCodecSt
}
->
maybe_check_replay
(
Header
),
maybe_check_replay
(
Header
),
...
@@ -314,6 +323,7 @@ parse_upstream_data(<<Header:64/binary, Rest/binary>>,
...
@@ -314,6 +323,7 @@ parse_upstream_data(<<Header:64/binary, Rest/binary>>,
mtp_secure_fake_tls
;
mtp_secure_fake_tls
;
{
false
,
_}
->
{
false
,
_}
->
assert_protocol
(
PacketLayerMod
),
assert_protocol
(
PacketLayerMod
),
check_policy
(
Listener
,
Ip
,
undefined
),
PacketLayerMod
PacketLayerMod
end
,
end
,
mtp_metric
:
count_inc
([
?
APP
,
protocol_ok
,
total
],
mtp_metric
:
count_inc
([
?
APP
,
protocol_ok
,
total
],
...
@@ -356,20 +366,19 @@ maybe_check_replay(Packet) ->
...
@@ -356,20 +366,19 @@ maybe_check_replay(Packet) ->
ok
ok
end
.
end
.
check_tls_
access
(_
Listener
,
_
Ip
,
#
{
sni_domain
:
=
Domain
})
->
check_tls_
policy
(
Listener
,
Ip
,
#
{
sni_domain
:
=
Tls
Domain
})
->
%% TODO validate timestamp!
%% TODO validate timestamp!
%% TODO some more scalable solution
check_policy
(
Listener
,
Ip
,
TlsDomain
);
case
application
:
get_env
(
?
APP
,
tls_allowed_domains
,
any
)
of
check_tls_policy
(_,
Ip
,
Meta
)
->
any
->
%% No limits
true
;
AllowedDomains
->
lists
:
member
(
Domain
,
AllowedDomains
)
orelse
error
({
protocol_error
,
tls_sni_domain_not_allowed
,
Domain
})
end
;
check_tls_access
(_,
Ip
,
Meta
)
->
error
({
protocol_error
,
tls_no_sni
,
{
Ip
,
Meta
}}).
error
({
protocol_error
,
tls_no_sni
,
{
Ip
,
Meta
}}).
check_policy
(
Listener
,
Ip
,
Domain
)
->
Rules
=
application
:
get_env
(
?
APP
,
policy
,
[]),
case
mtp_policy
:
check
(
Rules
,
Listener
,
Ip
,
Domain
)
of
[]
->
ok
;
[
Rule
|
_]
->
error
({
protocol_error
,
policy_error
,
{
Rule
,
Listener
,
Ip
,
Domain
}})
end
.
up_send
(
Packet
,
#state
{
stage
=
tunnel
,
codec
=
UpCodec
}
=
S
)
->
up_send
(
Packet
,
#state
{
stage
=
tunnel
,
codec
=
UpCodec
}
=
S
)
->
%% ?log(debug, ">Up: ~p", [Packet]),
%% ?log(debug, ">Up: ~p", [Packet]),
...
...
src/mtp_policy.erl
0 → 100644
View file @
5c8cc0c5
%%% @author Sergey <me@seriyps.ru>
%%% @copyright (C) 2019, Sergey
%%% @doc
%%% Evaluator for "policy" config
%%% @end
%%% Created : 20 Aug 2019 by Sergey <me@seriyps.ru>
-
module
(
mtp_policy
).
-
export
([
check
/
4
]).
-
export
([
dec
/
4
]).
-
export
([
convert
/
2
]).
-
export_type
([
rule
/
0
,
key
/
0
,
db_val
/
0
]).
-
record
(
vars
,
{
listener
::
atom
(),
client_ip
::
inet
:
ip_address
(),
ip_family
::
inet
|
inet6
,
tls_domain
::
undefined
|
binary
()}).
-
type
key
()
::
port_name
|
tls_domain
|
client_ipv4
|
client_ipv6
|
{
client_ipv4_subnet
,
1
..
32
}
|
{
client_ipv6_subnet
,
8
..
128
}.
-
type
rule
()
::
{
max_connections
,
[
key
()],
pos_integer
()}
|
{
in_table
,
key
(),
mtp_policy_table
:
sub_tab
()}
|
{
not_in_table
,
key
(),
mtp_policy_table
:
sub_tab
()}.
-
type
db_val
()
::
binary
()
|
integer
()
|
atom
().
-
include_lib
(
"hut/include/hut.hrl"
).
-
spec
check
([
rule
()],
any
(),
inet
:
ip_address
(),
binary
()
|
undefined
)
->
[
rule
()].
check
(
Rules
,
ListenerName
,
ClientIp
,
TlsDomain
)
->
Vars
=
vars
(
ListenerName
,
ClientIp
,
TlsDomain
),
lists
:
dropwhile
(
fun
(
Rule
)
->
try
check
(
Rule
,
Vars
)
catch
throw
:
not_applicable
->
true
end
end
,
Rules
).
dec
(
Rules
,
ListenerName
,
ClientIp
,
TlsDomain
)
->
%% FIXME: this is not idempotent if `check/4` returned `false`!
Vars
=
vars
(
ListenerName
,
ClientIp
,
TlsDomain
),
lists
:
foreach
(
fun
({
max_connections
,
Keys
,
_
Max
})
->
try
Key
=
[
val
(
K
,
Vars
)
||
K
<-
Keys
],
mtp_policy_counter
:
increment
(
Key
)
catch
throw
:
not_applicable
->
ok
end
;
(_)
->
ok
end
,
Rules
).
vars
(
ListenerName
,
ClientIp
,
TlsDomain
)
->
IpFamily
=
case
tuple_size
(
ClientIp
)
of
4
->
inet
;
8
->
inet6
end
,
#vars
{
listener
=
ListenerName
,
client_ip
=
ClientIp
,
ip_family
=
IpFamily
,
tls_domain
=
TlsDomain
}.
check
({
max_connections
,
Keys
,
Max
},
Vars
)
->
Key
=
[
val
(
K
,
Vars
)
||
K
<-
Keys
],
case
mtp_policy_counter
:
increment
(
Key
)
of
N
when
N
>
Max
->
false
;
_
->
true
end
;
check
({
in_table
,
Key
,
Tab
},
Vars
)
->
Val
=
val
(
Key
,
Vars
),
mtp_policy_table
:
exists
(
Tab
,
Val
);
check
({
not_in_table
,
Key
,
Tab
},
Vars
)
->
Val
=
val
(
Key
,
Vars
),
not
mtp_policy_table
:
exists
(
Tab
,
Val
).
val
(
port_name
=
T
,
#vars
{
listener
=
Listener
})
->
convert
(
T
,
Listener
);
val
(
tls_domain
=
T
,
#vars
{
tls_domain
=
Domain
})
when
is_binary
(
Domain
)
->
convert
(
T
,
Domain
);
val
(
client_ipv4
=
T
,
#vars
{
ip_family
=
inet
,
client_ip
=
Ip
})
->
convert
(
T
,
Ip
);
val
(
client_ipv6
=
T
,
#vars
{
ip_family
=
inet6
,
client_ip
=
Ip
})
->
convert
(
T
,
Ip
);
val
({
client_ipv4_subnet
,
Mask
}
=
T
,
#vars
{
ip_family
=
inet
,
client_ip
=
Ip
})
when
Mask
>
0
,
Mask
=<
32
->
convert
(
T
,
Ip
);
val
({
client_ipv6_subnet
,
Mask
}
=
T
,
#vars
{
ip_family
=
inet6
,
client_ip
=
Ip
})
when
Mask
>
8
,
Mask
=<
128
->
convert
(
T
,
Ip
);
val
(
Policy
,
Vars
)
when
is_atom
(
Policy
);
is_tuple
(
Policy
)
->
?
log
(
debug
,
"Policy
~p
not applicable
~p
"
,
[
Policy
,
Vars
]),
throw
(
not_applicable
).
-
spec
convert
(
key
(),
any
())
->
db_val
().
convert
(
port_name
,
PortName
)
->
PortName
;
convert
(
tls_domain
,
Domain
)
when
is_binary
(
Domain
)
->
Domain
;
convert
(
tls_domain
,
DomainStr
)
when
is_list
(
DomainStr
)
->
convert
(
tls_domain
,
list_to_binary
(
DomainStr
));
convert
(
client_ipv4
,
Ip
)
->
<<
I
:
32
/
unsigned
-
little
>>
=
mtp_rpc
:
inet_pton
(
Ip
),
I
;
convert
(
client_ipv6
,
Ip
)
->
<<
I
:
128
/
unsigned
-
little
>>
=
mtp_rpc
:
inet_pton
(
Ip
),
I
;
convert
({
client_ipv4_subnet
,
Mask
},
Ip
)
->
<<
I
:
Mask
/
unsigned
-
little
,
_
/
bits
>>
=
mtp_rpc
:
inet_pton
(
Ip
),
I
;
convert
({
client_ipv6_subnet
,
Mask
},
Ip
)
->
<<
I
:
Mask
/
unsigned
-
little
,
_
/
bits
>>
=
mtp_rpc
:
inet_pton
(
Ip
),
I
.
src/mtp_policy_counter.erl
0 → 100644
View file @
5c8cc0c5
%%%-------------------------------------------------------------------
%%% @author Sergey <me@seriyps.ru>
%%% @copyright (C) 2019, Sergey
%%% @doc
%%% Storage for `max_connections` policy.
%%% It's quite simple wrapper for public ETS counter.
%%% @end
%%% Created : 20 Aug 2019 by Sergey <me@seriyps.ru>
%%%-------------------------------------------------------------------
-
module
(
mtp_policy_counter
).
-
behaviour
(
gen_server
).
%% API
-
export
([
start_link
/
0
]).
-
export
([
increment
/
1
,
decrement
/
1
,
flush
/
0
]).
%% gen_server callbacks
-
export
([
init
/
1
,
handle_call
/
3
,
handle_cast
/
2
,
handle_info
/
2
,
terminate
/
2
,
code_change
/
3
]).
-
type
key
()
::
[
mtp_policy
:
db_val
()].
-
define
(
TAB
,
?
MODULE
).
-
record
(
state
,
{
tab
::
ets
:
tid
()}).
%%%===================================================================
%%% API
%%%===================================================================
-
spec
increment
(
key
())
->
integer
().
increment
(
Key
)
->
ets
:
update_counter
(
?
TAB
,
Key
,
1
,
{
Key
,
0
}).
-
spec
decrement
(
key
())
->
integer
().
decrement
(
Key
)
->
try
ets
:
update_counter
(
?
TAB
,
Key
,
1
)
of
New
when
New
=<
0
->
ets
:
delete
(
?
TAB
,
Key
),
0
;
New
->
New
catch
error
:
badarg
->
%% already removed
0
end
.
%% @doc Clean all counters
flush
()
->
gen_server
:
call
(
?
MODULE
,
flush
).
start_link
()
->
gen_server
:
start_link
({
local
,
?
MODULE
},
?
MODULE
,
[],
[]).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init
([])
->
Tab
=
ets
:
new
(
?
TAB
,
[
named_table
,
{
write_concurrency
,
true
},
public
]),
{
ok
,
#state
{
tab
=
Tab
}}.
handle_call
(
flush
,
_
From
,
#state
{
tab
=
Tab
}
=
State
)
->
true
=
ets
:
delete_all_objects
(
Tab
),
{
reply
,
ok
,
State
}.
handle_cast
(_
Msg
,
State
)
->
{
noreply
,
State
}.
handle_info
(_
Info
,
State
)
->
{
noreply
,
State
}.
terminate
(_
Reason
,
_
State
)
->
ok
.
code_change
(_
OldVsn
,
State
,
_
Extra
)
->
{
ok
,
State
}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
src/mtp_policy_table.erl
0 → 100644
View file @
5c8cc0c5
%%%-------------------------------------------------------------------
%%% @author Sergey <me@seriyps.ru>
%%% @copyright (C) 2019, Sergey
%%% @doc
%%% Storage for `in_table` and `not_in_table` policies. Implements 2-level nested set.
%%% It's quite simple wrapper for protected ETS set.
%%% @end
%%% Created : 20 Aug 2019 by Sergey <me@seriyps.ru>
%%%-------------------------------------------------------------------
-
module
(
mtp_policy_table
).
-
behaviour
(
gen_server
).
%% API
-
export
([
start_link
/
0
]).
-
export
([
add
/
3
,
del
/
3
,
exists
/
2
,
table_size
/
1
,
flush
/
0
]).
%% gen_server callbacks
-
export
([
init
/
1
,
handle_call
/
3
,
handle_cast
/
2
,
handle_info
/
2
,
terminate
/
2
,
code_change
/
3
]).
-
type
sub_tab
()
::
atom
().
-
type
value
()
::
mtp_policy
:
db_val
().
-
include_lib
(
"stdlib/include/ms_transform.hrl"
).
-
define
(
TAB
,
?
MODULE
).
-
record
(
state
,
{
tab
::
ets
:
tid
()}).
%%%===================================================================
%%% API
%%%===================================================================
-
spec
add
(
sub_tab
(),
mtp_policy
:
key
(),
value
())
->
ok
.
add
(
Subtable
,
Type
,
Value
)
->
gen_server
:
call
(
?
MODULE
,
{
add
,
Subtable
,
mtp_policy
:
convert
(
Type
,
Value
)}).
-
spec
del
(
sub_tab
(),
mtp_policy
:
key
(),
value
())
->
ok
.
del
(
Subtable
,
Type
,
Value
)
->
gen_server
:
call
(
?
MODULE
,
{
del
,
Subtable
,
mtp_policy
:
convert
(
Type
,
Value
)}).
-
spec
exists
(
sub_tab
(),
value
())
->
boolean
().
exists
(
Subtable
,
Value
)
->
case
ets
:
lookup
(
?
TAB
,
{
Subtable
,
Value
})
of
[]
->
false
;
[_]
->
true
end
.
-
spec
table_size
(
sub_tab
())
->
non_neg_integer
().
table_size
(
SubTable
)
->
MS
=
ets
:
fun2ms
(
fun
({{
Tab
,
_},
_})
when
Tab
=:=
SubTable
->
true
end
),
ets
:
select_count
(
?
TAB
,
MS
).
%% @doc Clean all counters
flush
()
->
gen_server
:
call
(
?
MODULE
,
flush
).
start_link
()
->
gen_server
:
start_link
({
local
,
?
MODULE
},
?
MODULE
,
[],
[]).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init
([])
->
Tab
=
ets
:
new
(
?
TAB
,
[
named_table
,
{
read_concurrency
,
true
},
protected
]),
{
ok
,
#state
{
tab
=
Tab
}}.
handle_call
({
add
,
SubTab
,
Data
},
_
From
,
#state
{
tab
=
Tab
}
=
State
)
->
true
=
ets
:
insert
(
Tab
,
{{
SubTab
,
Data
},
erlang
:
system_time
(
millisecond
)}),
{
reply
,
ok
,
State
};
handle_call
({
del
,
SubTab
,
Data
},
_
From
,
#state
{
tab
=
Tab
}
=
State
)
->
true
=
ets
:
delete
(
Tab
,
{
SubTab
,
Data
}),
{
reply
,
ok
,
State
};
handle_call
(
flush
,
_
From
,
#state
{
tab
=
Tab
}
=
State
)
->
true
=
ets
:
delete_all_objects
(
Tab
),
{
reply
,
ok
,
State
}.
handle_cast
(_
Msg
,
State
)
->
{
noreply
,
State
}.
handle_info
(_
Info
,
State
)
->
{
noreply
,
State
}.
terminate
(_
Reason
,
_
State
)
->
ok
.
code_change
(_
OldVsn
,
State
,
_
Extra
)
->
{
ok
,
State
}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
src/mtproto_proxy.app.src
View file @
5c8cc0c5
...
@@ -58,11 +58,13 @@
...
@@ -58,11 +58,13 @@
%% protocols will be immediately disallowed.
%% protocols will be immediately disallowed.
{allowed_protocols, [mtp_fake_tls, mtp_secure, mtp_abridged, mtp_intermediate]},
{allowed_protocols, [mtp_fake_tls, mtp_secure, mtp_abridged, mtp_intermediate]},
%% Which domains to allow in TLS SNI
%% Connection policies.
%% XXX: this option is experimental and will be removed later!
%% See README.md for documentation
%% Can be set to `any' to allow any domains.
%% {policy,
%% {tls_allowed_domains, any},
%% [{in_table, tls_domain, allowed_domains},
{tls_allowed_domains, [<<"en.wikipedia.org">>, <<"s3.amazonaws.com">>]},
%% {not_in_table, [client_ipv4], banned_ips},
%% {max_connections, [port_name, tls_domain], 15}
%% ]},
{init_dc_connections, 2},
{init_dc_connections, 2},
{clients_per_dc_connection, 300},
{clients_per_dc_connection, 300},
...
...
src/mtproto_proxy_app.erl
View file @
5c8cc0c5
...
@@ -172,13 +172,8 @@ build_urls(Host, Port, Secret, Protocols) ->
...
@@ -172,13 +172,8 @@ build_urls(Host, Port, Secret, Protocols) ->
end
,
Protocols
)),
end
,
Protocols
)),
lists
:
map
(
lists
:
map
(
fun
(
mtp_fake_tls
)
->
fun
(
mtp_fake_tls
)
->
%% Print just for 1st domain as example
Domain
=
<<
"s3.amazonaws.com"
>>
,
Domain
=
case
application
:
get_env
(
?
APP
,
tls_allowed_domains
)
of
ProtoSecret
=
mtp_fake_tls
:
format_secret_hex
(
Secret
,
Domain
),
{
ok
,
[
Domain0
|
_]}
->
Domain0
;
_
->
<<
"en.wikipedia.org"
>>
end
,
ProtoSecret
=
mtp_fake_tls
:
format_secret
(
Secret
,
Domain
),
MkUrl
(
ProtoSecret
);
MkUrl
(
ProtoSecret
);
(
mtp_secure
)
->
(
mtp_secure
)
->
ProtoSecret
=
[
"dd"
,
Secret
],
ProtoSecret
=
[
"dd"
,
Secret
],
...
...
src/mtproto_proxy_sup.erl
View file @
5c8cc0c5
...
@@ -54,6 +54,10 @@ init([]) ->
...
@@ -54,6 +54,10 @@ init([]) ->
#
{
id
=>
mtp_config
,
#
{
id
=>
mtp_config
,
start
=>
{
mtp_config
,
start_link
,
[]}},
start
=>
{
mtp_config
,
start_link
,
[]}},
#
{
id
=>
mtp_session_storage
,
#
{
id
=>
mtp_session_storage
,
start
=>
{
mtp_session_storage
,
start_link
,
[]}}
start
=>
{
mtp_session_storage
,
start_link
,
[]}},
#
{
id
=>
mtp_policy_table
,
start
=>
{
mtp_policy_table
,
start_link
,
[]}},
#
{
id
=>
mtp_policy_counter
,
start
=>
{
mtp_policy_counter
,
start_link
,
[]}}
],
],
{
ok
,
{
SupFlags
,
Childs
}}.
{
ok
,
{
SupFlags
,
Childs
}}.
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