Cosmetic changes: README, docker, dialyzer fixes

parent 0a1604d4
......@@ -9,7 +9,9 @@ Features
* Promoted channels! See `mtproto_proxy_app.src` `tag` option.
* "secure" randomized-packet-size protocol (34-symbol secrets starting with 'dd')
to prevent detection by DPI
* Secure-only mode (only allow connections with 'dd'-secrets). See `allowed_protocols` option.
* Fake-TLS protocol (base64 secrets) - another protocol to prevent DPI detection
* Secure-only mode (only allow connections with 'dd' or fake-tls-base64-secrets).
See `allowed_protocols` option.
* 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.
......@@ -47,7 +49,10 @@ Where
* `-p 443` / `MTP_PORT=…` proxy port
* `-s d0d6e111bada5511fcce9584deadbeef` / `MTP_SECRET=…` proxy secret (don't append `dd`! it should be 32 chars long!)
* `-t dcbe8f1493fa4cd9ab300891c0b5b326` / `MTP_TAG=…` ad-tag that you get from [@MTProxybot](https://t.me/MTProxybot)
* `-d` / `MTP_DD_ONLY=t` only allow "secure" connections (dd-secrets)
* `-a dd` / `MTP_DD_ONLY=t` only allow "secure" connections (dd-secrets)
* `-a tls` / `MTP_TLS_ONLY=t` only allow "fake-TLS" connections (base64 secrets)
It's ok to provide both `-a dd -a tls` to allow both protocols. If no `-a` option provided, all protocols will be allowed.
### To run with custom config-file
......@@ -234,6 +239,8 @@ Each section should have unique `name`!
### Only allow connections with 'dd'-secrets
This protocol uses randomized packet sizes, so it's more difficult to detect on DPI by
packet sizes.
It might be useful in Iran, where proxies are detected by DPI.
You should disable all protocols other than `mtp_secure` by providing `allowed_protocols` option:
......@@ -246,6 +253,22 @@ You should disable all protocols other than `mtp_secure` by providing `allowed_p
<..>
```
### Only allow fake-TLS connections with 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`:
```erlang
{mtproto_proxy,
[
{allowed_protocols, [mtp_fake_tls]},
{ports,
[#{name => mtp_handler_1,
<..>
```
### IPv6
Currently proxy only supports client connections via IPv6, but can only connect to Telegram servers
......
......@@ -67,7 +67,6 @@ do_decrypt(Data, Tail, #baes_st{decrypt = {DecKey, DecIv}} = S) ->
NewDecIv = crypto:next_iv(aes_cbc, Data),
{Decrypted, Tail, S#baes_st{decrypt = {DecKey, NewDecIv}}}.
%% To comply mtp_layer interface
try_decode_packet(Bin, S) ->
case decrypt(Bin, S) of
{<<>>, _Tail, S1} ->
......
......@@ -180,10 +180,7 @@ handle_info({tcp, Sock, Data}, #state{sock = Sock, transport = Transport,
{ok, S1} ->
ok = Transport:setopts(Sock, [{active, once}]),
%% Consider checking health here as well
{noreply, bump_timer(S1)};
{error, Reason} ->
?log(info, "handle_data error ~p", [Reason]),
{stop, normal, S}
{noreply, bump_timer(S1)}
catch error:{protocol_error, Type, Extra} ->
mtp_metric:count_inc([?APP, protocol_error, total], 1, #{labels => [Type]}),
?log(warning, "~s: protocol_error ~p ~p", [inet:ntoa(Ip), Type, Extra]),
......@@ -265,11 +262,9 @@ state_timeout(stop) ->
%% Handle telegram client -> proxy stream
handle_upstream_data(Bin, #state{stage = tunnel,
codec = UpCodec} = S) ->
?log(debug, "tunneling ~p; codec=~p", [Bin, UpCodec]),
{ok, S3, UpCodec1} =
mtp_codec:fold_packets(
fun(Decoded, S1, Codec1) ->
?log(debug, "raw tunneled packet ~p", [Decoded]),
mtp_metric:histogram_observe(
[?APP, tg_packet_size, bytes],
byte_size(Decoded),
......@@ -279,20 +274,16 @@ handle_upstream_data(Bin, #state{stage = tunnel,
end, S, Bin, UpCodec),
{ok, S3#state{codec = UpCodec1}};
handle_upstream_data(Bin, #state{codec = Codec0} = S0) ->
?log(debug, "Codec0: ~p", [Codec0]),
{ok, S, Codec} =
mtp_codec:fold_packets(
fun(Decoded, S1, Codec1) ->
?log(debug, "Codec1: ~p, unfolded: ~p", [Codec1, Decoded]),
case parse_upstream_data(Decoded, S1#state{codec = Codec1}) of
{ok, S2} ->
?log(debug, "Codec2: ~p", [S2#state.codec]),
{S2, S2#state.codec};
{error, Err} ->
error(Err)
end
end, S0, Bin, Codec0),
?log(debug, "Codec: ~p", [Codec]),
case mtp_codec:is_empty(Codec) of
true ->
{ok, S#state{codec = Codec}};
......@@ -304,20 +295,14 @@ handle_upstream_data(Bin, #state{codec = Codec0} = S0) ->
parse_upstream_data(<<?TLS_START, _/binary>> = AllData,
#state{stage = tls_hello, secret = Secret, codec = Codec0} = S) when
byte_size(AllData) >= (?TLS_CLIENT_HELLO_LEN + 5) ->
{ok, AllowedProtocols} = application:get_env(?APP, allowed_protocols),
lists:member(mtp_fake_tls, AllowedProtocols) orelse
error({protocol_error, disabled_protocol, mtp_fake_tls}),
assert_protocol(mtp_fake_tls),
<<Data:(?TLS_CLIENT_HELLO_LEN + 5)/binary, Tail/binary>> = AllData,
case mtp_fake_tls:from_client_hello(Data, Secret) of
{ok, Response, SessionId, Timestamp, TlsCodec} ->
maybe_check_tls_replay(SessionId, Timestamp),
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}};
{error, _} = Err ->
Err
end;
{ok, Response, SessionId, Timestamp, TlsCodec} = mtp_fake_tls:from_client_hello(Data, Secret),
maybe_check_tls_replay(SessionId, Timestamp),
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}};
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>>,
......@@ -326,19 +311,20 @@ parse_upstream_data(<<Header:64/binary, Rest/binary>>,
case mtp_obfuscated:from_header(Header, Secret) of
{ok, DcId, PacketLayerMod, CryptoCodecSt} ->
maybe_check_replay(Header),
ProtoToReport = case mtp_codec:info(tls, Codec0) of
{true, _} when PacketLayerMod == mtp_secure ->
mtp_secure_fake_tls;
{false, _} ->
PacketLayerMod
end,
ProtoToReport =
case mtp_codec:info(tls, Codec0) of
{true, _} when PacketLayerMod == mtp_secure ->
mtp_secure_fake_tls;
{false, _} ->
assert_protocol(PacketLayerMod),
PacketLayerMod
end,
mtp_metric:count_inc([?APP, protocol_ok, total],
1, #{labels => [Listener, ProtoToReport]}),
Codec1 = mtp_codec:replace(crypto, mtp_obfuscated, CryptoCodecSt, Codec0),
PacketCodec = PacketLayerMod:new(),
Codec2 = mtp_codec:replace(packet, PacketLayerMod, PacketCodec, Codec1),
Codec = mtp_codec:push_back(crypto, Rest, Codec2),
?log(debug, "Hdr=~p, codec=~p", [Header, Codec]),
Opts = #{ad_tag => Tag,
addr => Addr},
{RealDcId, Pool, Downstream} = mtp_config:get_downstream_safe(DcId, Opts),
......@@ -358,6 +344,11 @@ parse_upstream_data(Bin, #state{stage = Stage, codec = Codec0} = S) when Stage =
Codec = mtp_codec:push_back(first, Bin, Codec0),
{ok, S#state{codec = Codec}}.
assert_protocol(Protocol) ->
{ok, AllowedProtocols} = application:get_env(?APP, allowed_protocols),
lists:member(Protocol, AllowedProtocols)
orelse error({protocol_error, disabled_protocol, Protocol}).
maybe_check_replay(Packet) ->
%% Check for session replay attack: attempt to connect with the same 1st 64byte packet
case application:get_env(?APP, replay_check_session_storage, off) of
......
......@@ -10,7 +10,6 @@
-export([client_create/3,
client_create/4,
from_header/2,
from_header/3,
new/4,
encrypt/2,
decrypt/2,
......@@ -38,7 +37,7 @@ client_create(Secret, Protocol, DcId) ->
client_create(crypto:strong_rand_bytes(58),
Secret, Protocol, DcId).
-spec client_create(binary(), binary(), mtp_layer:codec(), integer()) ->
-spec client_create(binary(), binary(), mtp_codec:packet_codec(), integer()) ->
{Packet,
{EncKey, EncIv},
{DecKey, DecIv},
......@@ -93,13 +92,9 @@ encode_dc_id(DcId) ->
<<DcId:16/signed-little-integer>>.
%% @doc creates new obfuscated stream (MTProto proxy format)
from_header(Header, Secret) ->
{ok, AllowedProtocols} = application:get_env(?APP, allowed_protocols),
from_header(Header, Secret, AllowedProtocols).
-spec from_header(binary(), binary()) -> {ok, integer(), mtp_layer:codec(), codec()}
| {error, unknown_protocol | disabled_protocol}.
from_header(Header, Secret, AllowedProtocols) when byte_size(Header) == 64 ->
-spec from_header(binary(), binary()) -> {ok, integer(), mtp_codec:packet_codec(), codec()}
| {error, unknown_protocol}.
from_header(Header, Secret) when byte_size(Header) == 64 ->
%% 1) Encryption key
%% [--- _: 8b ----|---------- b: 48b -------------|-- _: 8b --] = header: 64b
%% b_r: 48b = reverse([---------- b ------------------])
......@@ -124,13 +119,8 @@ from_header(Header, Secret, AllowedProtocols) when byte_size(Header) == 64 ->
{error, unknown_protocol} = Err ->
Err;
Protocol ->
case lists:member(Protocol, AllowedProtocols) of
true ->
DcId = get_dc(Bin1),
{ok, DcId, Protocol, St1};
false ->
{error, disabled_protocol}
end
DcId = get_dc(Bin1),
{ok, DcId, Protocol, St1}
end.
init_up_encrypt(Bin, Secret) ->
......@@ -173,7 +163,6 @@ decrypt(Encrypted, #st{decrypt = Dec} = St) ->
{Dec1, Data} = crypto:stream_encrypt(Dec, Encrypted),
{Data, <<>>, St#st{decrypt = Dec1}}.
%% To comply with mtp_layer interface
-spec try_decode_packet(iodata(), codec()) -> {ok, Decoded :: binary(), Tail :: binary(), codec()}
| {incomplete, codec()}.
try_decode_packet(Encrypted, St) ->
......@@ -198,7 +187,7 @@ client_server_test() ->
DcId = 4,
Protocol = mtp_secure,
{Packet, _, _, _CliCodec} = client_create(Secret, Protocol, DcId),
Srv = from_header(Packet, Secret, [Protocol]),
Srv = from_header(Packet, Secret),
?assertMatch({ok, DcId, Protocol, _}, Srv).
-endif.
......@@ -52,11 +52,11 @@
{num_acceptors, 60},
{max_connections, 40960},
%% It's possible to forbid connection from telegram client to proxy
%% with some of the protocols. Might be useful to set this to
%% only `{allowed_protocols, [mtp_secure]}` if you want to only allow
%% connections to this proxy with "dd"-secrets. Connections by other
%% protocols will be immediately closed.
{allowed_protocols, [mtp_abridged, mtp_intermediate, mtp_secure, mtp_fake_tls]},
%% with some of the protocols. Ti's recommended to set this to
%% only `{allowed_protocols, [mtp_secure, mtp_fake_tls]}` because those
%% protocols are more resistant to DPI detection. Connections by other
%% protocols will be immediately disallowed.
{allowed_protocols, [mtp_fake_tls, mtp_secure, mtp_abridged, mtp_intermediate]},
{init_dc_connections, 2},
{clients_per_dc_connection, 300},
......
......@@ -34,6 +34,7 @@ PORT=${MTP_PORT:-""}
SECRET=${MTP_SECRET:-""}
TAG=${MTP_TAG:-""}
DD_ONLY=${MTP_DD_ONLY:-""}
TLS_ONLY=${MTP_TLS_ONLY:-""}
# check command line options
while getopts "p:s:t:dh" o; do
......@@ -47,7 +48,17 @@ while getopts "p:s:t:dh" o; do
t)
TAG=${OPTARG}
;;
a)
if [ "${OPTARG}" -e "dd" ]; then
DD_ONLY="y"
elif [ "${OPTARG}" -eq "tls" ]; then
TLS_ONLY="y"
else
error "Invalid -a value: '${OPTARG}'"
fi
;;
d)
echo "Warning: -d is deprecated! use '-a dd' instead"
DD_ONLY="y"
;;
h)
......@@ -56,10 +67,14 @@ while getopts "p:s:t:dh" o; do
esac
done
DD_ARG=""
PROTO_ARG=""
if [ -n "${DD_ONLY}" ]; then
DD_ARG='-mtproto_proxy allowed_protocols [mtp_secure]'
if [ -n "${DD_ONLY}" -a -n "${TLS_ONLY}" ]; then
PROTO_ARG="-mtproto_proxy allowed_protocols [mtp_fake_tls, mtp_secure]"
elif [ -n "${DD_ONLY}" ]; then
PROTO_ARG='-mtproto_proxy allowed_protocols [mtp_secure]'
elif [ -n "${TLS_ONLY}" ]; then
PROTO_ARG='-mtproto_proxy allowed_protocols [mtp_fake_tls]'
fi
# if at least one option is set...
......@@ -76,7 +91,7 @@ if [ -n "${PORT}" -o -n "${SECRET}" -o -n "${TAG}" ]; then
[ -n "`echo $TAG | grep -x '[[:xdigit:]]\{32\}'`" ] || \
error "Invalid tag. Should be 32 chars of 0-9 a-f"
exec $CMD $DD_ARG -mtproto_proxy ports "[#{name => mtproto_proxy, port => $PORT, secret => <<\"$SECRET\">>, tag => <<\"$TAG\">>}]"
exec $CMD $PROTO_ARG -mtproto_proxy ports "[#{name => mtproto_proxy, port => $PORT, secret => <<\"$SECRET\">>, tag => <<\"$TAG\">>}]"
else
exec $CMD $DD_ARG
exec $CMD $PROTO_ARG
fi
......@@ -52,7 +52,7 @@ cs_hs_exchange(Secret, DcId, Protocol) ->
%% io:format("Secret: ~p; DcId: ~p, Protocol: ~p~n",
%% [Secret, DcId, Protocol]),
{Packet, _, _, _CliCodec} = mtp_obfuscated:client_create(Secret, Protocol, DcId),
case mtp_obfuscated:from_header(Packet, Secret, [Protocol]) of
case mtp_obfuscated:from_header(Packet, Secret) of
{ok, DcId, Protocol, _SrvCodec} ->
true;
_ ->
......@@ -78,7 +78,7 @@ cs_stream_exchange(Secret, DcId, Protocol, Stream) ->
%% io:format("Secret: ~p; DcId: ~p, Protocol: ~p~n",
%% [Secret, DcId, Protocol]),
{Header, _, _, CliCodec} = mtp_obfuscated:client_create(Secret, Protocol, DcId),
{ok, DcId, Protocol, SrvCodec} = mtp_obfuscated:from_header(Header, Secret, [Protocol]),
{ok, DcId, Protocol, SrvCodec} = mtp_obfuscated:from_header(Header, Secret),
%% Client to server
{CliCodec1,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment