mirror of
https://github.com/seriyps/mtproto_proxy.git
synced 2026-05-13 08:46:46 +00:00
Cosmetic changes: README, docker, dialyzer fixes
This commit is contained in:
parent
0a1604d4be
commit
418709208c
7 changed files with 80 additions and 63 deletions
27
README.md
27
README.md
|
|
@ -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},
|
||||
|
|
|
|||
25
start.sh
25
start.sh
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue