mirror of
https://github.com/seriyps/mtproto_proxy.git
synced 2026-08-04 07:08:10 +00:00
Experimental support for connection policies
This commit is contained in:
parent
dcdf05a318
commit
5c8cc0c50f
9 changed files with 464 additions and 45 deletions
121
README.md
121
README.md
|
|
@ -6,19 +6,22 @@ This part of code was extracted from [@socksy_bot](https://t.me/socksy_bot).
|
|||
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')
|
||||
to prevent detection by DPI
|
||||
* Fake-TLS protocol (base64 secrets) - another protocol to prevent DPI detection
|
||||
* Secure-only mode (only allow connections with 'dd' or fake-tls-base64-secrets).
|
||||
* Fake-TLS protocol ('ee'/base64 secrets) - another protocol to prevent DPI detection
|
||||
* Secure-only mode (only allow connections with 'dd' or fake-TLS).
|
||||
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
|
||||
* Very high performance - can handle tens of thousands connections! Scales to all CPU cores.
|
||||
1Gbps, 90k connections on 4-core/8Gb RAM cloud server.
|
||||
* 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
|
||||
* 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
|
||||
* Small codebase compared to official one, code is covered by automated tests
|
||||
* A lots of metrics could be exported (optional)
|
||||
|
|
@ -27,20 +30,20 @@ How to install - one-line interactive installer
|
|||
-----------------------------------------------
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
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
|
||||
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.
|
||||
|
||||
How to start - docker
|
||||
|
|
@ -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
|
||||
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
|
||||
|
|
@ -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
|
||||
HTTPS web traffic (technically speaking, TLSv1.3 + HTTP/2).
|
||||
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
|
||||
{mtproto_proxy,
|
||||
|
|
@ -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;\nDomain: ~s\n", [Secret, Domain]).'
|
||||
```
|
||||
|
||||
### IPv6
|
||||
|
||||
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
|
|||
|
||||
this may make proxy slower, it can start to consume bit more CPU, will be vulnerable to replay attacks,
|
||||
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),
|
||||
it will use less CPU and will be better protected from replay attacks, but will use more RAM:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
-behaviour(mtp_codec).
|
||||
|
||||
-export([format_secret/2]).
|
||||
-export([format_secret_base64/2,
|
||||
format_secret_hex/2]).
|
||||
-export([from_client_hello/2,
|
||||
new/0,
|
||||
try_decode_packet/2,
|
||||
|
|
@ -79,11 +80,16 @@
|
|||
|
||||
|
||||
%% @doc format TLS secret
|
||||
-spec format_secret(binary(), binary()) -> binary().
|
||||
format_secret(Secret, Domain) when byte_size(Secret) == 16 ->
|
||||
format_secret_hex(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>>);
|
||||
format_secret(HexSecret, Domain) when byte_size(HexSecret) == 32 ->
|
||||
format_secret(mtp_handler:unhex(HexSecret), Domain).
|
||||
format_secret_base64(HexSecret, Domain) when byte_size(HexSecret) == 32 ->
|
||||
format_secret_base64(mtp_handler:unhex(HexSecret), Domain).
|
||||
|
||||
base64url(Bin) ->
|
||||
%% see https://hex.pm/packages/base64url
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
ad_tag :: binary(),
|
||||
addr :: mtp_config:netloc_v4v6(), % IP/Port of remote side
|
||||
policy_state :: any(),
|
||||
started_at :: pos_integer(),
|
||||
timer_state = init :: init | hibernate | stop,
|
||||
timer :: gen_timeout:tout(),
|
||||
|
|
@ -212,7 +213,14 @@ handle_info(Other, S) ->
|
|||
?log(warning, "Unexpected msg ~p", [Other]),
|
||||
{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),
|
||||
mtp_metric:count_inc([?APP, in_connection_closed, total], 1, #{labels => [Listener]}),
|
||||
Lifetime = erlang:system_time(millisecond) - Started,
|
||||
|
|
@ -295,16 +303,17 @@ parse_upstream_data(<<?TLS_START, _/binary>> = AllData,
|
|||
assert_protocol(mtp_fake_tls),
|
||||
<<Data:(?TLS_CLIENT_HELLO_LEN + 5)/binary, Tail/binary>> = AllData,
|
||||
{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),
|
||||
Codec = mtp_codec:push_back(tls, Tail, Codec1),
|
||||
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(Data, S#state{stage = tls_hello});
|
||||
parse_upstream_data(<<Header:64/binary, Rest/binary>>,
|
||||
#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
|
||||
{ok, DcId, PacketLayerMod, CryptoCodecSt} ->
|
||||
maybe_check_replay(Header),
|
||||
|
|
@ -314,6 +323,7 @@ parse_upstream_data(<<Header:64/binary, Rest/binary>>,
|
|||
mtp_secure_fake_tls;
|
||||
{false, _} ->
|
||||
assert_protocol(PacketLayerMod),
|
||||
check_policy(Listener, Ip, undefined),
|
||||
PacketLayerMod
|
||||
end,
|
||||
mtp_metric:count_inc([?APP, protocol_ok, total],
|
||||
|
|
@ -356,20 +366,19 @@ maybe_check_replay(Packet) ->
|
|||
ok
|
||||
end.
|
||||
|
||||
check_tls_access(_Listener, _Ip, #{sni_domain := Domain}) ->
|
||||
check_tls_policy(Listener, Ip, #{sni_domain := TlsDomain}) ->
|
||||
%% TODO validate timestamp!
|
||||
%% TODO some more scalable solution
|
||||
case application:get_env(?APP, tls_allowed_domains, any) of
|
||||
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) ->
|
||||
check_policy(Listener, Ip, TlsDomain);
|
||||
check_tls_policy(_, 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) ->
|
||||
%% ?log(debug, ">Up: ~p", [Packet]),
|
||||
|
|
|
|||
132
src/mtp_policy.erl
Normal file
132
src/mtp_policy.erl
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
%%% @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.
|
||||
|
||||
|
||||
80
src/mtp_policy_counter.erl
Normal file
80
src/mtp_policy_counter.erl
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%%% @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
|
||||
%%%===================================================================
|
||||
94
src/mtp_policy_table.erl
Normal file
94
src/mtp_policy_table.erl
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%%% @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
|
||||
%%%===================================================================
|
||||
|
|
@ -58,11 +58,13 @@
|
|||
%% protocols will be immediately disallowed.
|
||||
{allowed_protocols, [mtp_fake_tls, mtp_secure, mtp_abridged, mtp_intermediate]},
|
||||
|
||||
%% Which domains to allow in TLS SNI
|
||||
%% XXX: this option is experimental and will be removed later!
|
||||
%% Can be set to `any' to allow any domains.
|
||||
%% {tls_allowed_domains, any},
|
||||
{tls_allowed_domains, [<<"en.wikipedia.org">>, <<"s3.amazonaws.com">>]},
|
||||
%% Connection policies.
|
||||
%% See README.md for documentation
|
||||
%% {policy,
|
||||
%% [{in_table, tls_domain, allowed_domains},
|
||||
%% {not_in_table, [client_ipv4], banned_ips},
|
||||
%% {max_connections, [port_name, tls_domain], 15}
|
||||
%% ]},
|
||||
|
||||
{init_dc_connections, 2},
|
||||
{clients_per_dc_connection, 300},
|
||||
|
|
|
|||
|
|
@ -172,13 +172,8 @@ build_urls(Host, Port, Secret, Protocols) ->
|
|||
end, Protocols)),
|
||||
lists:map(
|
||||
fun(mtp_fake_tls) ->
|
||||
%% Print just for 1st domain as example
|
||||
Domain = case application:get_env(?APP, tls_allowed_domains) of
|
||||
{ok, [Domain0 | _]} -> Domain0;
|
||||
_ ->
|
||||
<<"en.wikipedia.org">>
|
||||
end,
|
||||
ProtoSecret = mtp_fake_tls:format_secret(Secret, Domain),
|
||||
Domain = <<"s3.amazonaws.com">>,
|
||||
ProtoSecret = mtp_fake_tls:format_secret_hex(Secret, Domain),
|
||||
MkUrl(ProtoSecret);
|
||||
(mtp_secure) ->
|
||||
ProtoSecret = ["dd", Secret],
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ init([]) ->
|
|||
#{id => mtp_config,
|
||||
start => {mtp_config, start_link, []}},
|
||||
#{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}}.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue