1: %%% eturnal STUN/TURN server.
    2: %%%
    3: %%% Copyright (c) 2020-2023 Holger Weiss <holger@zedat.fu-berlin.de>.
    4: %%% Copyright (c) 2020-2023 ProcessOne, SARL.
    5: %%% All rights reserved.
    6: %%%
    7: %%% Licensed under the Apache License, Version 2.0 (the "License");
    8: %%% you may not use this file except in compliance with the License.
    9: %%% You may obtain a copy of the License at
   10: %%%
   11: %%%     http://www.apache.org/licenses/LICENSE-2.0
   12: %%%
   13: %%% Unless required by applicable law or agreed to in writing, software
   14: %%% distributed under the License is distributed on an "AS IS" BASIS,
   15: %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   16: %%% See the License for the specific language governing permissions and
   17: %%% limitations under the License.
   18: 
   19: -module(eturnal_SUITE).
   20: -compile(export_all).
   21: -include_lib("common_test/include/ct.hrl").
   22: 
   23: -type info() :: ct_suite:ct_info().
   24: -type config() :: ct_suite:ct_config().
   25: -type test_def() :: ct_suite:ct_test_def().
   26: -type test_name() :: ct_suite:ct_testname().
   27: -type group_def() :: ct_suite:ct_group_def().
   28: -type group_name() :: ct_suite:ct_groupname().
   29: 
   30: %% API.
   31: 
   32: -spec suite() -> [info()].
   33: suite() ->
   34:     [{require, {server, [address, udp_port, tcp_port, tls_port, auto_port]}},
   35:      {timetrap, {seconds, 120}}].
   36: 
   37: -spec init_per_suite(config()) -> config().
   38: init_per_suite(Config) ->
   39:     Server = ct:get_config(server),
   40:     Address = proplists:get_value(address, Server),
   41:     UdpPort = proplists:get_value(udp_port, Server),
   42:     TcpPort = proplists:get_value(tcp_port, Server),
   43:     TlsPort = proplists:get_value(tls_port, Server),
   44:     AutoPort = proplists:get_value(auto_port, Server),
   45:     [{address, Address},
   46:      {udp_port, UdpPort},
   47:      {tcp_port, TcpPort},
   48:      {tls_port, TlsPort},
   49:      {auto_port, AutoPort} | Config].
   50: 
   51: -spec end_per_suite(config()) -> ok.
   52: end_per_suite(_Config) ->
   53:     ok.
   54: 
   55: -spec init_per_group(group_name(), config()) -> config().
   56: init_per_group(_GroupName, Config) ->
   57:     Config.
   58: 
   59: -spec end_per_group(group_name(), config()) -> ok.
   60: end_per_group(_GroupName, _Config) ->
   61:     ok.
   62: 
   63: -spec init_per_testcase(test_name(), config()) -> config().
   64: -ifdef(old_inet_backend).
   65: init_per_testcase(start_eturnal, Config) ->
   66:     set_eturnal_env("eturnal-old-otp.yml", Config);
   67: init_per_testcase(stun_tcp_auto, _Config) ->
   68:     {skip, otp_version_unsupported};
   69: init_per_testcase(stun_tls_auto, _Config) ->
   70:     {skip, otp_version_unsupported};
   71: init_per_testcase(_TestCase, Config) ->
   72:     Config.
   73: -else.
   74: init_per_testcase(start_eturnal, Config) ->
   75:     set_eturnal_env("eturnal-new-otp.yml", Config);
   76: init_per_testcase(_TestCase, Config) ->
   77:     Config.
   78: -endif.
   79: 
   80: -spec end_per_testcase(test_name(), config()) -> ok.
   81: end_per_testcase(_TestCase, _Config) ->
   82:     ok.
   83: 
   84: -spec groups() -> [group_def()].
   85: groups() ->
   86:     [].
   87: 
   88: -spec all() -> [test_def()] | {skip, term()}.
   89: all() ->
   90:     [start_eturnal,
   91:      check_status,
   92:      check_info,
   93:      check_all_sessions,
   94:      check_user_sessions,
   95:      check_disconnect,
   96:      check_credentials,
   97:      check_loglevel,
   98:      check_version,
   99:      reload,
  100:      connect_tcp,
  101:      connect_tls,
  102:      turn_udp,
  103:      stun_udp,
  104:      stun_tcp,
  105:      stun_tls,
  106:      stun_tcp_auto,
  107:      stun_tls_auto,
  108:      stop_eturnal].
  109: 
  110: -spec start_eturnal(config()) -> any().
  111: start_eturnal(_Config) ->
  112:     ct:pal("Starting up eturnal"),
  113:     ok = eturnal:start().
  114: 
  115: -spec check_status(config()) -> any().
  116: check_status(_Config) ->
  117:     ct:pal("Checking eturnal status"),
  118:     {ok, Status} = eturnal_ctl:get_status(),
  119:     ct:pal("Got eturnal status: ~p", [Status]),
  120:     true = is_list(Status).
  121: 
  122: -spec check_info(config()) -> any().
  123: check_info(_Config) ->
  124:     ct:pal("Checking eturnal statistics"),
  125:     {ok, Info} = eturnal_ctl:get_info(),
  126:     ct:pal("Got eturnal statistics: ~p", [Info]),
  127:     true = is_list(Info).
  128: 
  129: -spec check_all_sessions(config()) -> any().
  130: check_all_sessions(_Config) ->
  131:     ct:pal("Checking active TURN sessions"),
  132:     {ok, Sessions} = eturnal_ctl:get_sessions(),
  133:     ct:pal("Got active TURN sessions: ~p", [Sessions]),
  134:     true = is_list(Sessions).
  135: 
  136: -spec check_user_sessions(config()) -> any().
  137: check_user_sessions(_Config) ->
  138:     ct:pal("Checking active TURN sessions of a user"),
  139:     {ok, Sessions1} = eturnal_ctl:get_sessions("alice"),
  140:     ct:pal("Got active TURN sessions of a user: ~p", [Sessions1]),
  141:     true = is_list(Sessions1),
  142:     ct:pal("Checking active TURN sessions of another user"),
  143:     {ok, Sessions2} = eturnal_ctl:get_sessions("1256900400:alice"),
  144:     ct:pal("Got active TURN sessions of another user: ~p", [Sessions2]),
  145:     true = is_list(Sessions2),
  146:     ct:pal("Checking active TURN sessions of invalid user"),
  147:     {error, Reason} = eturnal_ctl:get_sessions(alice),
  148:     ct:pal("Got an error, as expected: ~p", [Reason]).
  149: 
  150: -spec check_disconnect(config()) -> any().
  151: check_disconnect(_Config) ->
  152:     ct:pal("Checking disconnection of TURN user"),
  153:     {ok, Msg} = eturnal_ctl:disconnect("alice"),
  154:     ct:pal("Got result for disconnecting TURN user: ~p", [Msg]),
  155:     true = is_list(Msg),
  156:     ct:pal("Checking disconnection of invalid TURN user"),
  157:     {error, Reason} = eturnal_ctl:disconnect(alice),
  158:     ct:pal("Got an error, as expected: ~p", [Reason]).
  159: 
  160: -spec check_credentials(config()) -> any().
  161: check_credentials(_Config) ->
  162:     Timestamp = "2009-10-30 11:00:00Z",
  163:     Username = 1256900400,
  164:     Password = "uEKlpcME7MNMMVRV8rUFPCTIFEs=",
  165:     ct:pal("Checking credentials valid until ~s", [Timestamp]),
  166:     {ok, Credentials} = eturnal_ctl:get_credentials(Timestamp, []),
  167:     {ok, [Username, Password], []} =
  168:         io_lib:fread("Username: ~u~~nPassword: ~s", Credentials),
  169:     lists:foreach(
  170:       fun(Lifetime) ->
  171:               ct:pal("Checking credentials valid for ~s", [Lifetime]),
  172:               {ok, Creds} = eturnal_ctl:get_credentials(Lifetime, "alice"),
  173:               {ok, [Time, Pass], []} =
  174:                   io_lib:fread("Username: ~u:alice~~nPassword: ~s", Creds),
  175:               {ok, Pass} =
  176:                   eturnal_ctl:get_password(integer_to_list(Time) ++ ":alice"),
  177:               true = erlang:system_time(second) + 86400 - Time < 5
  178:       end, ["86400", "86400s", "1440m", "24h", "1d"]),
  179:     ct:pal("Checking invalid expiry"),
  180:     lists:foreach(
  181:       fun(Invalid) ->
  182:               {error, _Reason1} = eturnal_ctl:get_password(Invalid),
  183:               {error, _Reason2} = eturnal_ctl:get_credentials(Invalid, [])
  184:       end, ["Invalid", invalid, [invalid]]),
  185:     ct:pal("Checking invalid suffix"),
  186:     {error, _Reason} = eturnal_ctl:get_credentials(Username, invalid),
  187:     ct:pal("Checking static credentials"),
  188:     {ok, "l0vesBob"} = eturnal_ctl:get_password("alice").
  189: 
  190: -spec check_loglevel(config()) -> any().
  191: check_loglevel(_Config) ->
  192:     Level = "debug",
  193:     ct:pal("Setting log level to ~s", [Level]),
  194:     ok = eturnal_ctl:set_loglevel(list_to_atom(Level)),
  195:     ct:pal("Checking whether log level is set to ~s", [Level]),
  196:     {ok, Level} = eturnal_ctl:get_loglevel(),
  197:     ct:pal("Setting invalid log level"),
  198:     {error, _Reason} = eturnal_ctl:set_loglevel(invalid),
  199:     ct:pal("Checking whether log level is still set to ~s", [Level]),
  200:     {ok, Level} = eturnal_ctl:get_loglevel().
  201: 
  202: -spec check_version(config()) -> any().
  203: check_version(_Config) ->
  204:     ct:pal("Checking eturnal version"),
  205:     {ok, Version} = eturnal_ctl:get_version(),
  206:     ct:pal("Got eturnal version: ~p", [Version]),
  207:     match = re:run(Version, "^[0-9]+\\.[0-9]+\\.[0-9](\\+[0-9]+)?",
  208:                    [{capture, none}]).
  209: 
  210: -spec reload(config()) -> any().
  211: reload(_Config) ->
  212:     CertFile = <<"run/cert.pem">>,
  213:     ct:pal("Deleting TLS certificate"),
  214:     ok = file:delete(CertFile),
  215:     ct:pal("Reloading eturnal"),
  216:     ok = eturnal_ctl:reload(),
  217:     ct:pal("Checking whether new TLS certificate was created"),
  218:     {ok, _} = file:read_file_info(CertFile),
  219:     ct:pal("Reloading eturnal without changes"),
  220:     ok = eturnal_ctl:reload().
  221: 
  222: -spec connect_tcp(config()) -> any().
  223: connect_tcp(Config) ->
  224:     Addr = ?config(address, Config),
  225:     Port = ?config(tcp_port, Config),
  226:     ct:pal("Connecting to 127.0.0.1:~B (TCP)", [Port]),
  227:     {ok, Sock} = gen_tcp:connect(Addr, Port, []),
  228:     ok = gen_tcp:close(Sock).
  229: 
  230: -spec connect_tls(config()) -> any().
  231: connect_tls(Config) ->
  232:     Addr = ?config(address, Config),
  233:     Port = ?config(tls_port, Config),
  234:     ct:pal("Connecting to 127.0.0.1:~B (TLS)", [Port]),
  235:     {ok, TCPSock} = gen_tcp:connect(Addr, Port, []),
  236:     {ok, TLSSock} = fast_tls:tcp_to_tls(TCPSock, [connect]),
  237:     ok = fast_tls:close(TLSSock).
  238: 
  239: -spec turn_udp(config()) -> any().
  240: turn_udp(Config) ->
  241:     Addr = ?config(address, Config),
  242:     Port = ?config(udp_port, Config),
  243:     Username1 = <<"alice">>,
  244:     Password1 = <<"l0vesBob">>,
  245:     Username2 = <<"2145913200">>,
  246:     Password2 = <<"cLwpKS2/9bWHf+agUImD47PIXNE=">>,
  247:     Realm = <<"eturnal.net">>,
  248:     ct:pal("Allocating TURN relay on 127.0.0.1:~B (UDP)", [Port]),
  249:     ok = stun_test:allocate_udp(Addr, Port, Username1, Realm, Password1),
  250:     ok = stun_test:allocate_udp(Addr, Port, Username2, Realm, Password2).
  251: 
  252: -spec stun_udp(config()) -> any().
  253: stun_udp(Config) ->
  254:     Addr = ?config(address, Config),
  255:     Port = ?config(udp_port, Config),
  256:     ct:pal("Performing STUN query against 127.0.0.1:~B (UDP)", [Port]),
  257:     Result = stun_test:bind_udp(Addr, Port),
  258:     ct:pal("Got query result: ~p", [Result]),
  259:     true = is_tuple(Result),
  260:     true = element(1, Result) =:= stun.
  261: 
  262: -spec stun_tcp(config()) -> any().
  263: stun_tcp(Config) ->
  264:     Addr = ?config(address, Config),
  265:     Port = ?config(tcp_port, Config),
  266:     ct:pal("Performing STUN query against 127.0.0.1:~B (TCP)", [Port]),
  267:     Result = stun_test:bind_tcp(Addr, Port),
  268:     ct:pal("Got query result: ~p", [Result]),
  269:     true = is_tuple(Result),
  270:     true = element(1, Result) =:= stun.
  271: 
  272: -spec stun_tls(config()) -> any().
  273: stun_tls(Config) ->
  274:     Addr = ?config(address, Config),
  275:     Port = ?config(tls_port, Config),
  276:     ct:pal("Performing STUN query against 127.0.0.1:~B (TLS)", [Port]),
  277:     Result = stun_test:bind_tls(Addr, Port),
  278:     ct:pal("Got query result: ~p", [Result]),
  279:     true = is_tuple(Result),
  280:     true = element(1, Result) =:= stun.
  281: 
  282: -spec stun_tcp_auto(config()) -> any().
  283: stun_tcp_auto(Config) ->
  284:     Addr = ?config(address, Config),
  285:     Port = ?config(auto_port, Config),
  286:     ct:pal("Performing STUN query against 127.0.0.1:~B (TCP)", [Port]),
  287:     Result = stun_test:bind_tcp(Addr, Port),
  288:     ct:pal("Got query result: ~p", [Result]),
  289:     true = is_tuple(Result),
  290:     true = element(1, Result) =:= stun.
  291: 
  292: -spec stun_tls_auto(config()) -> any().
  293: stun_tls_auto(Config) ->
  294:     Addr = ?config(address, Config),
  295:     Port = ?config(auto_port, Config),
  296:     ct:pal("Performing STUN query against 127.0.0.1:~B (TLS)", [Port]),
  297:     Result = stun_test:bind_tls(Addr, Port),
  298:     ct:pal("Got query result: ~p", [Result]),
  299:     true = is_tuple(Result),
  300:     true = element(1, Result) =:= stun.
  301: 
  302: -spec stop_eturnal(config()) -> any().
  303: stop_eturnal(_Config) ->
  304:     ct:pal("Stopping eturnal"),
  305:     ok = eturnal:stop().
  306: 
  307: %% Internal functions.
  308: 
  309: -spec set_eturnal_env(file:filename_all(), config()) -> config().
  310: set_eturnal_env(ConfName, Config) ->
  311:     DataDir = ?config(data_dir, Config),
  312:     ConfFile = filename:join(DataDir, ConfName),
  313:     ok = application:set_env(conf, file, ConfFile),
  314:     ok = application:set_env(conf, on_fail, stop),
  315:     ok = application:set_env(eturnal, on_fail, exit),
  316:     Config.