Add support for IPv6 clients. gh-11

parent 42eb63f2
sudo: required
language: erlang language: erlang
otp_release: otp_release:
- 22.0 - 22.0
...@@ -6,6 +7,8 @@ otp_release: ...@@ -6,6 +7,8 @@ otp_release:
- 20.3 - 20.3
#- 19.3 not supported (string:lexemes/2) #- 19.3 not supported (string:lexemes/2)
#- 18.3 not supported (string:lexemes/2, tricky binary comprehension, map typespec with `:=`, ?assertEqual/3) #- 18.3 not supported (string:lexemes/2, tricky binary comprehension, map typespec with `:=`, ?assertEqual/3)
install:
- sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0
script: script:
- ./rebar3 compile - ./rebar3 compile
- ./rebar3 xref - ./rebar3 xref
......
...@@ -246,6 +246,38 @@ You should disable all protocols other than `mtp_secure` by providing `allowed_p ...@@ -246,6 +246,38 @@ You should disable all protocols other than `mtp_secure` by providing `allowed_p
<..> <..>
``` ```
### IPv6
Currently proxy only supports client connections via IPv6, but can only connect to Telegram servers
using IPv4.
To enable IPv6, you should put IPv6 address in `listen_ip` config key.
If you want proxy to accept clients on the same port with both IPv4 and IPv6, you should
have 2 `ports` sections with the same `port`, `secret` and `tag`, but with different names and
different `listen_ip` (one v4 and one v6):
```erlang
{mtproto_proxy,
%% see src/mtproto_proxy.app.src for examples.
[
{ports,
[#{name => mtp_handler_all_ipv4,
listen_ip => "0.0.0.0", % IPv4 address, eg 203.0.113.1
port => 1443,
secret => <<"d0d6e111bada5511fcce9584deadbeef">>,
tag => <<"dcbe8f1493fa4cd9ab300891c0b5b326">>},
#{name => mtp_handler_all_ipv6,
listen_ip => "::", % IPv6 address, eg "2001:db8:85a3::8a2e:370:7334"
port => 1443,
secret => <<"d0d6e111bada5511fcce9584deadbeef">>,
tag => <<"dcbe8f1493fa4cd9ab300891c0b5b326">>}
]}
]},
{lager,
<...>
```
### Tune resource consumption ### Tune resource consumption
If your server have low amount of RAM, try to set If your server have low amount of RAM, try to set
......
...@@ -26,9 +26,11 @@ ...@@ -26,9 +26,11 @@
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-export_type([netloc_v4v6/0]).
-type dc_id() :: integer(). -type dc_id() :: integer().
-type netloc() :: {inet:ip4_address(), inet:port_number()}. -type netloc() :: {inet:ip4_address(), inet:port_number()}.
-type netloc_v4v6() :: {inet:ip_address(), inet:port_number()}.
-include_lib("hut/include/hut.hrl"). -include_lib("hut/include/hut.hrl").
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
-endif. -endif.
-type handle() :: pid(). -type handle() :: pid().
-type upstream_opts() :: #{addr := mtp_config:netloc(), % IP/Port of TG client -type upstream_opts() :: #{addr := mtp_config:netloc_v4v6(), % IP/Port of TG client
ad_tag => binary()}. ad_tag => binary()}.
-type upstream() :: { -type upstream() :: {
_UpsStatic ::{_ConnId :: mtp_rpc:conn_id(), _UpsStatic ::{_ConnId :: mtp_rpc:conn_id(),
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
dc_id :: {DcId :: integer(), Pool :: pid()} | undefined, dc_id :: {DcId :: integer(), Pool :: pid()} | undefined,
ad_tag :: binary(), ad_tag :: binary(),
addr :: mtp_config:netloc(), % IP/Port of remote side addr :: mtp_config:netloc_v4v6(), % IP/Port of remote side
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(),
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
port := inet:port_number(), port := inet:port_number(),
secret := binary(), secret := binary(),
tag := binary(), tag := binary(),
listen_ip => inet:ip4_addr()}. listen_ip => string()}.
%%==================================================================== %%====================================================================
%% API %% API
...@@ -69,11 +69,16 @@ running_ports() -> ...@@ -69,11 +69,16 @@ running_ports() ->
ip := Ip, ip := Ip,
port := Port} = maps:from_list(Opts), port := Port} = maps:from_list(Opts),
[Name, Secret, AdTag] = ProtoOpts, [Name, Secret, AdTag] = ProtoOpts,
#{name => Name, case inet:ntoa(Ip) of
listen_ip => inet:ntoa(Ip), {error, einval} ->
port => Port, error({invalid_ip, Ip});
secret => Secret, IpAddr ->
tag => AdTag} #{name => Name,
listen_ip => IpAddr,
port => Port,
secret => Secret,
tag => AdTag}
end
end, mtp_listeners()). end, mtp_listeners()).
%%==================================================================== %%====================================================================
...@@ -84,14 +89,19 @@ start_proxy(#{name := Name, port := Port, secret := Secret, tag := Tag} = P) -> ...@@ -84,14 +89,19 @@ start_proxy(#{name := Name, port := Port, secret := Secret, tag := Tag} = P) ->
ListenIpStr = maps:get( ListenIpStr = maps:get(
listen_ip, P, listen_ip, P,
application:get_env(?APP, listen_ip, "0.0.0.0")), application:get_env(?APP, listen_ip, "0.0.0.0")),
{ok, ListenIp} = inet:parse_ipv4_address(ListenIpStr), {ok, ListenIp} = inet:parse_address(ListenIpStr),
Family = case tuple_size(ListenIp) of
4 -> inet;
8 -> inet6
end,
NumAcceptors = application:get_env(?APP, num_acceptors, 60), NumAcceptors = application:get_env(?APP, num_acceptors, 60),
MaxConnections = application:get_env(?APP, max_connections, 10240), MaxConnections = application:get_env(?APP, max_connections, 10240),
Res = Res =
ranch:start_listener( ranch:start_listener(
Name, ranch_tcp, Name, ranch_tcp,
#{socket_opts => [{ip, ListenIp}, #{socket_opts => [{ip, ListenIp},
{port, Port}], {port, Port},
Family],
num_acceptors => NumAcceptors, num_acceptors => NumAcceptors,
max_connections => MaxConnections}, max_connections => MaxConnections},
mtp_handler, [Name, Secret, Tag]), mtp_handler, [Name, Secret, Tag]),
......
...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
downstream_qlen_backpressure_case/1, downstream_qlen_backpressure_case/1,
config_change_case/1, config_change_case/1,
replay_attack_case/1, replay_attack_case/1,
replay_attack_server_error_case/1 replay_attack_server_error_case/1,
ipv6_connect_case/1
]). ]).
-export([set_env/2, -export([set_env/2,
...@@ -375,26 +376,55 @@ replay_attack_server_error_case(Cfg) when is_list(Cfg) -> ...@@ -375,26 +376,55 @@ replay_attack_server_error_case(Cfg) when is_list(Cfg) ->
%% TODO: send a lot, not read, and then close - assert connection IDs are cleaned up %% TODO: send a lot, not read, and then close - assert connection IDs are cleaned up
%% @doc Test that it's possible to connect and communicate via IPv6
ipv6_connect_case({pre, Cfg}) ->
setup_single(?FUNCTION_NAME, "::1", 10000 + ?LINE, #{}, Cfg);
ipv6_connect_case({post, Cfg}) ->
stop_single(Cfg);
ipv6_connect_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),
ConnCount = fun() ->
mtp_test_metric:get_tags(
count, [?APP, in_connection, total], [?FUNCTION_NAME])
end,
?assertEqual(not_found, ConnCount()),
?assertEqual(8, tuple_size(Host)),
Cli0 = mtp_test_client:connect(Host, Port, Secret, DcId, mtp_secure),
Data = crypto:strong_rand_bytes(64),
Cli1 = mtp_test_client:send(Data, Cli0),
{ok, Packet, Cli2} = mtp_test_client:recv_packet(Cli1, 1000),
ok = mtp_test_client:close(Cli2),
?assertEqual(Data, Packet),
?assertEqual(1, ConnCount()),
ok = mtp_test_metric:wait_for_value(
count, [?APP, in_connection_closed, total], [?FUNCTION_NAME], 1, 5000).
%% Helpers %% Helpers
setup_single(Name, MtpPort, DcCfg0, Cfg) -> setup_single(Name, MtpPort, DcCfg0, Cfg) ->
setup_single(Name, "127.0.0.1", MtpPort, DcCfg0, Cfg).
setup_single(Name, MtpIpStr, MtpPort, DcCfg0, Cfg) ->
{ok, Pid} = mtp_test_metric:start_link(), {ok, Pid} = mtp_test_metric:start_link(),
PubKey = crypto:strong_rand_bytes(128), PubKey = crypto:strong_rand_bytes(128),
DcId = 1, DcId = 1,
Ip = {127, 0, 0, 1}, DcConf = [{DcId, {127, 0, 0, 1}, MtpPort + 10}],
DcConf = [{DcId, Ip, MtpPort + 10}],
Secret = mtp_handler:hex(crypto:strong_rand_bytes(16)), Secret = mtp_handler:hex(crypto:strong_rand_bytes(16)),
Listeners = [#{name => Name, Listeners = [#{name => Name,
port => MtpPort, port => MtpPort,
listen_ip => "127.0.0.1", listen_ip => MtpIpStr,
secret => Secret, secret => Secret,
tag => <<"dcbe8f1493fa4cd9ab300891c0b5b326">>}], tag => <<"dcbe8f1493fa4cd9ab300891c0b5b326">>}],
application:load(mtproto_proxy), application:load(mtproto_proxy),
Cfg1 = set_env([{ports, Listeners}], Cfg), Cfg1 = set_env([{ports, Listeners}], Cfg),
{ok, DcCfg} = mtp_test_datacenter:start_dc(PubKey, DcConf, DcCfg0), {ok, DcCfg} = mtp_test_datacenter:start_dc(PubKey, DcConf, DcCfg0),
{ok, _} = application:ensure_all_started(mtproto_proxy), {ok, _} = application:ensure_all_started(mtproto_proxy),
{ok, MtpIp} = inet:parse_address(MtpIpStr),
[{dc_id, DcId}, [{dc_id, DcId},
{mtp_host, Ip}, {mtp_host, MtpIp},
{mtp_port, MtpPort}, {mtp_port, MtpPort},
{mtp_secret, Secret}, {mtp_secret, Secret},
{dc_conf, DcCfg}, {dc_conf, DcCfg},
......
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