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.