Modernisations

* Add variable-length ClientHello
* Make it compile on OTP 27+
* Upgrade OTP versions in CI
This commit is contained in:
Sergey Prokhorov 2026-02-17 01:04:19 +01:00
parent f9c2d32d4f
commit 0cc2e02c8c
No known key found for this signature in database
GPG key ID: 1C570244E4EF3337
10 changed files with 126 additions and 33 deletions

View file

@ -33,7 +33,7 @@ connect(Host, Port, Secret, DcId, Protocol) ->
-spec connect(inet:socket_address() | inet:hostname(),
inet:port_number(),
binary(), binary(), integer(),
mtp_codec:packet_codec() | {mtp_fake_tls, binary()}) -> client().
mtp_codec:packet_codec() | {mtp_fake_tls, binary()} | {mtp_fake_tls, binary(), pos_integer()}) -> client().
connect(Host, Port, Seed, Secret, DcId, Protocol0) ->
Opts = [{packet, raw},
{mode, binary},
@ -51,6 +51,14 @@ connect(Host, Port, Seed, Secret, DcId, Protocol0) ->
%% TODO: if Tail is not empty, use codec:push_back(first, ..)
{_HS, _CC, _D, <<>>} = mtp_fake_tls:parse_server_hello(ServerHello),
{mtp_secure, true, mtp_fake_tls:new()};
{mtp_fake_tls, Domain, TlsPacketLen} ->
ClientHello = mtp_fake_tls:make_client_hello(Secret, Domain, TlsPacketLen),
ok = gen_tcp:send(Sock, ClientHello),
%% Let's hope whole server hello will arrive in a single chunk
{ok, ServerHello} = gen_tcp:recv(Sock, 0, 5000),
%% TODO: if Tail is not empty, use codec:push_back(first, ..)
{_HS, _CC, _D, <<>>} = mtp_fake_tls:parse_server_hello(ServerHello),
{mtp_secure, true, mtp_fake_tls:new()};
_ -> {Protocol0, false, undefined}
end,
{Header0, _, _, CryptoLayer} = mtp_obfuscated:client_create(Seed, Secret, Protocol, DcId),

View file

@ -3,7 +3,7 @@
-include_lib("proper/include/proper.hrl").
-include_lib("stdlib/include/assert.hrl").
-export([prop_codec_small/1, prop_codec_big/1, prop_stream/1]).
-export([prop_codec_small/1, prop_codec_big/1, prop_stream/1, prop_variable_length_hello/1]).
prop_codec_small(doc) ->
"Tests that any binary below 65535 bytes can be encoded and decoded back as single frame".
@ -60,3 +60,28 @@ decode_stream(BinStream, Codec, Acc) ->
{ok, DecPacket, Tail, Codec1} ->
decode_stream(Tail, Codec1, [DecPacket | Acc])
end.
prop_variable_length_hello(doc) ->
"Tests that ClientHello with various packet lengths can be parsed correctly".
prop_variable_length_hello() ->
?FORALL({TlsPacketLen, Secret, Domain},
{proper_types:integer(512, 4096),
proper_types:binary(16),
<<"example.com">>},
variable_length_hello(TlsPacketLen, Secret, Domain)).
variable_length_hello(TlsPacketLen, Secret, Domain) ->
Timestamp = erlang:system_time(second),
SessionId = crypto:strong_rand_bytes(32),
ClientHello = mtp_fake_tls:make_client_hello(Timestamp, SessionId, Secret, Domain, TlsPacketLen),
%% Verify packet has correct length
?assertEqual(5 + TlsPacketLen, byte_size(ClientHello)),
%% Verify handshake can be parsed
{ok, _Response, Meta, _Codec} = mtp_fake_tls:from_client_hello(ClientHello, Secret),
%% Verify metadata
?assertEqual(SessionId, maps:get(session_id, Meta)),
?assertEqual(Timestamp, maps:get(timestamp, Meta)),
?assertEqual(Domain, maps:get(sni_domain, Meta)),
true.

View file

@ -13,6 +13,7 @@
echo_secure_case/1,
echo_abridged_many_packets_case/1,
echo_tls_case/1,
echo_tls_long_hello_case/1,
ipv6_connect_case/1,
packet_too_large_case/1,
policy_max_conns_case/1,
@ -138,6 +139,25 @@ echo_tls_case(Cfg) when is_list(Cfg) ->
ok = mtp_test_client:close(Cli1).
%% @doc Test TLS handshake with long ClientHello (2000 bytes) to simulate newer Telegram clients
echo_tls_long_hello_case({pre, Cfg}) ->
setup_single(?FUNCTION_NAME, 10000 + ?LINE, #{}, Cfg);
echo_tls_long_hello_case({post, Cfg}) ->
stop_single(Cfg);
echo_tls_long_hello_case(Cfg) when is_list(Cfg) ->
DcId = ?config(dc_id, Cfg),
Host = ?config(mtp_host, Cfg),
Port = ?config(mtp_port, Cfg),
Secret = ?config(mtp_secret, Cfg),
%% Test with 2000-byte ClientHello (newer Telegram clients send longer packets)
Cli0 = mtp_test_client:connect(Host, Port, Secret, DcId, {mtp_fake_tls, <<"example.com">>, 2000}),
Cli1 = ping(Cli0),
?assertEqual(
1, mtp_test_metric:get_tags(
count, [?APP, protocol_ok, total], [?FUNCTION_NAME, mtp_secure_fake_tls])),
ok = mtp_test_client:close(Cli1).
%% @doc test that client trying to send too big packets will be force-disconnected
packet_too_large_case({pre, Cfg}) ->
setup_single(?FUNCTION_NAME, 10000 + ?LINE, #{}, Cfg);