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.