/home/runner/work/eturnal/eturnal/_build/test/cover/aggregate/eturnal.html

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).
20 -behaviour(gen_server).
21 -export([start/0,
22 stop/0]).
23 -export([start_link/0]).
24 -export([init/1,
25 handle_call/3,
26 handle_cast/2,
27 handle_info/2,
28 terminate/2,
29 code_change/3]).
30 -export([init_config/0,
31 config_is_loaded/0,
32 run_hook/2,
33 get_password/2,
34 get_opt/1,
35 create_self_signed/1,
36 reload/3,
37 abort/1]).
38 -export_type([transport/0,
39 option/0,
40 value/0,
41 config_changes/0,
42 state/0]).
43
44 -ifdef(EUNIT).
45 -include_lib("eunit/include/eunit.hrl").
46 -endif.
47 -include_lib("kernel/include/logger.hrl").
48 -define(PEM_FILE_NAME, "cert.pem").
49
50 -record(eturnal_state,
51 {listeners :: listeners(),
52 modules :: modules()}).
53
54 -type transport() :: udp | tcp | tls | auto.
55 -type option() :: atom().
56 -type value() :: term().
57 -type config_changes() :: {[{option(), value()}],
58 [{option(), value()}],
59 [option()]}.
60
61 -opaque state() :: #eturnal_state{}.
62
63 -type listeners() :: [{inet:ip_address(), inet:port_number(), transport()}].
64 -type modules() :: [module()].
65
66 %% API: non-release startup and shutdown (used by test suite).
67
68 -spec start() -> ok | {error, term()}.
69 start() ->
70 1 case application:ensure_all_started(eturnal) of
71 {ok, _Started} ->
72 1 ok;
73 {error, _Reason} = Err ->
74
:-(
Err
75 end.
76
77 -spec stop() -> ok | {error, term()}.
78 stop() ->
79 1 application:stop(eturnal).
80
81 %% API: supervisor callback.
82
83 -spec start_link() -> {ok, pid()} | ignore | {error, term()}.
84 start_link() ->
85 1 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
86
87 %% API: gen_server callbacks.
88
89 -spec init(any()) -> {ok, state()}.
90 init(_Opts) ->
91 1 process_flag(trap_exit, true),
92 1 ok = eturnal_module:init(),
93 1 ok = log_relay_addresses(),
94 1 ok = log_control_listener(),
95 1 try
96 1 ok = ensure_run_dir(),
97 1 ok = check_turn_config(),
98 1 ok = check_proxy_config(),
99 1 _R = check_pem_file()
100 catch exit:Reason1 ->
101
:-(
abort(Reason1)
102 end,
103 1 try {start_modules(), start_listeners()} of
104 {Modules, Listeners} ->
105 1 ?LOG_DEBUG("Started ~B modules", [length(Modules)]),
106 1 ?LOG_DEBUG("Started ~B listeners", [length(Listeners)]),
107 1 {ok, #eturnal_state{listeners = Listeners, modules = Modules}}
108 catch exit:Reason2 ->
109
:-(
abort(Reason2)
110 end.
111
112 -spec handle_call(reload | get_status | get_info | get_version | get_loglevel |
113 {set_loglevel, eturnal_logger:level()} |
114 {get_password, binary()} | term(),
115 {pid(), term()}, state())
116 -> {reply, ok | {ok, term()} | {error, term()}, state()}.
117 handle_call(reload, _From, State) ->
118 2 case reload(State) of
119 {ok, State1} ->
120 2 {reply, ok, State1};
121 {error, _Reason} = Err ->
122
:-(
{reply, Err, State}
123 end;
124 handle_call(get_status, _From, State) ->
125
:-(
{reply, ok, State};
126 handle_call(get_info, _From, State) ->
127 1 Info = eturnal_misc:info(),
128 1 {reply, {ok, Info}, State};
129 handle_call(get_version, _From, State) ->
130 1 Version = eturnal_misc:version(),
131 1 {reply, {ok, Version}, State};
132 handle_call(get_loglevel, _From, State) ->
133 2 Level = eturnal_logger:get_level(),
134 2 {reply, {ok, Level}, State};
135 handle_call({set_loglevel, Level}, _From, State) ->
136 1 try
137 1 ok = eturnal_logger:set_level(Level),
138 1 {reply, ok, State}
139 catch error:{badmatch, {error, _Reason} = Err} ->
140
:-(
{reply, Err, State}
141 end;
142 handle_call({get_password, Username}, _From, State) ->
143 13 case {get_opt(secret), is_dynamic_username(Username)} of
144 {[Secret | _Secrets], true} ->
145 11 Password = derive_password(Username, [Secret]),
146 11 {reply, {ok, Password}, State};
147 {_, _} ->
148 2 case maps:get(Username, get_opt(credentials), undefined) of
149 Password when is_binary(Password) ->
150 1 {reply, {ok, Password}, State};
151 undefined ->
152 1 {reply, {error, no_credentials}, State}
153 end
154 end;
155 handle_call(Request, From, State) ->
156
:-(
?LOG_ERROR("Got unexpected request from ~p: ~p", [From, Request]),
157
:-(
{reply, {error, badarg}, State}.
158
159 -spec handle_cast(reload |
160 {config_change, config_changes(),
161 fun(() -> ok), fun(() -> ok)} | term(), state())
162 -> {noreply, state()}.
163 handle_cast(reload, State) ->
164
:-(
case reload(State) of
165 {ok, State1} ->
166
:-(
{noreply, State1};
167 {error, _Reason} ->
168
:-(
{noreply, State}
169 end;
170 handle_cast({config_change, Changes, BeginFun, EndFun}, State) ->
171
:-(
ok = BeginFun(),
172
:-(
State1 = apply_config_changes(State, Changes),
173
:-(
ok = EndFun(),
174
:-(
{noreply, State1};
175 handle_cast(Msg, State) ->
176
:-(
?LOG_ERROR("Got unexpected message: ~p", [Msg]),
177
:-(
{noreply, State}.
178
179 -spec handle_info(term(), state()) -> {noreply, state()}.
180 handle_info(Info, State) ->
181
:-(
?LOG_ERROR("Got unexpected info: ~p", [Info]),
182
:-(
{noreply, State}.
183
184 -spec terminate(normal | shutdown | {shutdown, term()} | term(), state()) -> ok.
185 terminate(Reason, State) ->
186 1 ?LOG_DEBUG("Terminating ~s (~p)", [?MODULE, Reason]),
187 1 try stop_listeners(State)
188 catch exit:Reason1 ->
189
:-(
?LOG_ERROR(format_error(Reason1))
190 end,
191 1 try stop_modules(State)
192 catch exit:Reason2 ->
193
:-(
?LOG_ERROR(format_error(Reason2))
194 end,
195 1 try clean_run_dir()
196 catch exit:Reason3 ->
197
:-(
?LOG_ERROR(format_error(Reason3))
198 end,
199 1 _ = eturnal_module:terminate(),
200 1 ok.
201
202 -spec code_change({down, term()} | term(), state(), term()) -> {ok, state()}.
203 code_change(_OldVsn, State, _Extra) ->
204
:-(
?LOG_NOTICE("Upgraded to eturnal ~s, reapplying configuration",
205
:-(
[eturnal_misc:version()]),
206
:-(
ok = reload_config(),
207
:-(
{ok, State}.
208
209 %% API: (re)load configuration.
210
211 -spec init_config() -> ok.
212 init_config() -> % Just to cope with an empty configuration file.
213 1 case config_is_loaded() of
214 true ->
215 1 ?LOG_DEBUG("Configuration has been loaded successfully"),
216 1 ok;
217 false ->
218
:-(
?LOG_DEBUG("Empty configuration, using defaults"),
219
:-(
ok = conf:load([{eturnal, []}])
220 end.
221
222 -spec config_is_loaded() -> boolean().
223 config_is_loaded() ->
224 1 try eturnal:get_opt(realm) of
225 Realm when is_binary(Realm) ->
226 1 true
227 catch error:{badmatch, undefined} ->
228
:-(
false
229 end.
230
231 %% API: stun callbacks.
232
233 -spec run_hook(eturnal_module:event(), eturnal_module:info()) -> ok.
234 run_hook(Event, Info) ->
235 9 eturnal_module:handle_event(Event, Info).
236
237 -spec get_password(binary(), binary())
238 -> binary() | [binary()] | {expired, binary() | [binary()]}.
239 get_password(Username, _Realm) ->
240 4 [Expiration | _Suffix] = binary:split(Username, <<$:>>),
241 4 try binary_to_integer(Expiration) of
242 ExpireTime ->
243 2 case erlang:system_time(second) of
244 Now when Now < ExpireTime ->
245 2 ?LOG_DEBUG("Deriving password for: ~ts", [Username]),
246 2 derive_password(Username, get_opt(secret));
247 Now when Now >= ExpireTime ->
248
:-(
case get_opt(strict_expiry) of
249 true ->
250
:-(
?LOG_INFO("Credentials expired: ~ts", [Username]),
251
:-(
<<>>;
252 false ->
253
:-(
?LOG_DEBUG("Credentials expired: ~ts", [Username]),
254
:-(
{expired,
255 derive_password(Username, get_opt(secret))}
256 end
257 end
258 catch _:badarg ->
259 2 ?LOG_DEBUG("Looking up password for: ~ts", [Username]),
260 2 case maps:get(Username, get_opt(credentials), undefined) of
261 Password when is_binary(Password) ->
262 2 Password;
263 undefined ->
264
:-(
?LOG_INFO("Have no password for: ~ts", [Username]),
265
:-(
<<>>
266 end
267 end.
268
269 %% API: retrieve option value.
270
271 -spec get_opt(option()) -> value().
272 get_opt(Opt) ->
273 120 {ok, Val} = application:get_env(eturnal, Opt),
274 120 Val.
275
276 %% API: create self-signed certificate.
277
278 -spec create_self_signed(file:filename_all()) -> ok.
279 create_self_signed(File) ->
280 2 try
281 2 PEM = eturnal_cert:create(get_opt(realm)),
282 2 ok = touch(File),
283 2 ok = file:write_file(File, PEM, [raw])
284 catch error:{_, {error, Reason}} ->
285
:-(
exit({pem_failure, File, Reason})
286 end.
287
288 %% API: reload service.
289
290 -spec reload(config_changes(), fun(() -> ok), fun(() -> ok)) -> ok.
291 reload(ConfigChanges, BeginFun, EndFun) ->
292
:-(
Msg = {config_change, ConfigChanges, BeginFun, EndFun},
293
:-(
ok = gen_server:cast(?MODULE, Msg).
294
295 %% API: abnormal termination.
296
297 -spec abort(term()) -> no_return().
298 abort(Reason) ->
299
:-(
case application:get_env(eturnal, on_fail, halt) of
300 exit ->
301
:-(
?LOG_CRITICAL("Stopping: ~s", [format_error(Reason)]),
302
:-(
exit(Reason);
303 _Halt ->
304
:-(
?LOG_CRITICAL("Aborting: ~s", [format_error(Reason)]),
305
:-(
eturnal_logger:flush(),
306
:-(
halt(1)
307 end.
308
309 %% Internal functions: reload configuration.
310
311 -spec reload_config() -> ok.
312 reload_config() ->
313
:-(
ok = gen_server:cast(?MODULE, reload).
314
315 %% Internal functions: authentication.
316
317 -spec is_dynamic_username(binary()) -> boolean().
318 is_dynamic_username(Username) ->
319 13 case string:to_integer(Username) of
320 {N, <<":", _Rest/binary>>} when is_integer(N), N > 0 ->
321 10 true;
322 {N, <<>>} when is_integer(N), N > 0 ->
323 1 true;
324 {_, _} ->
325 2 false
326 end.
327
328 -spec derive_password(binary(), [binary()]) -> binary() | [binary()].
329 -ifdef(old_crypto).
330 derive_password(Username, [Secret]) ->
331 base64:encode(crypto:hmac(sha, Secret, Username));
332 derive_password(Username, Secrets) when is_list(Secrets) ->
333 [derive_password(Username, [Secret]) || Secret <- Secrets].
334 -else.
335 derive_password(Username, [Secret]) ->
336 13 base64:encode(crypto:mac(hmac, sha, Secret, Username));
337 derive_password(Username, Secrets) when is_list(Secrets) ->
338
:-(
[derive_password(Username, [Secret]) || Secret <- Secrets].
339 -endif.
340
341 %% Internal functions: log relay address(es) and distribution listener port.
342
343 -spec log_relay_addresses() -> ok.
344 log_relay_addresses() ->
345 1 Min = get_opt(relay_min_port),
346 1 Max = get_opt(relay_max_port),
347 1 case get_opt(relay_ipv4_addr) of
348 {_, _, _, _} = Addr4 ->
349 1 ?LOG_INFO("Relay IPv4 address: ~s (port range: ~B-~B)",
350
:-(
[inet:ntoa(Addr4), Min, Max]);
351 undefined ->
352
:-(
?LOG_INFO("Relay IPv4 address not configured")
353 end,
354 1 case get_opt(relay_ipv6_addr) of
355 {_, _, _, _, _, _, _, _} = Addr6 ->
356
:-(
?LOG_INFO("Relay IPv6 address: ~s (port range: ~B-~B)",
357
:-(
[inet:ntoa(Addr6), Min, Max]);
358 undefined ->
359 1 ?LOG_INFO("Relay IPv6 address not configured")
360 end.
361
362 -spec log_control_listener() -> ok.
363 -dialyzer({[no_fail_call, no_match], log_control_listener/0}). % OTP 21/22.
364 log_control_listener() ->
365 1 [Name, Host] = string:split(atom_to_list(node()), "@"),
366 % The 'catch' calms Dialyzer on OTP 21 (even though we don't match 'EXIT').
367 1 case catch erl_epmd:port_please(Name, Host, timer:seconds(10)) of
368 {port, Port, Version} ->
369
:-(
?LOG_INFO("Listening on ~s:~B (tcp) (Erlang protocol version ~B)",
370
:-(
[Host, Port, Version]);
371 {error, Reason} ->
372
:-(
?LOG_INFO("Cannot determine control query port: ~p", [Reason]);
373 Reason when is_atom(Reason) ->
374 1 ?LOG_INFO("Cannot determine control query port: ~s", [Reason])
375 end.
376
377 %% Internal functions: module startup/shutdown.
378
379 -spec start_modules() -> modules().
380 start_modules() ->
381 3 lists:map(
382 fun({Mod, _Opts}) ->
383 9 case eturnal_module:start(Mod) of
384 ok ->
385 9 ?LOG_INFO("Started ~s", [Mod]),
386 9 Mod;
387 {error, Reason} ->
388
:-(
exit({module_failure, start, Mod, Reason})
389 end
390 end, maps:to_list(get_opt(modules))).
391
392 -spec stop_modules(state()) -> ok.
393 stop_modules(#eturnal_state{modules = Modules}) ->
394 3 lists:foreach(
395 fun(Mod) ->
396 9 case eturnal_module:stop(Mod) of
397 ok ->
398 9 ?LOG_INFO("Stopped ~s", [Mod]);
399 {error, Reason} ->
400
:-(
exit({module_failure, stop, Mod, Reason})
401 end
402 end, Modules).
403
404 %% Internal functions: listener startup/shutdown.
405
406 -spec start_listeners() -> listeners().
407 start_listeners() ->
408 1 Opts = lists:filtermap(
409 fun({InKey, OutKey}) ->
410 9 opt_filter({OutKey, get_opt(InKey)})
411 end, opt_map()) ++ [{auth_fun, fun ?MODULE:get_password/2},
412 {hook_fun, fun ?MODULE:run_hook/2}]
413 ++ blacklist_opts()
414 ++ whitelist_opts(),
415 1 lists:map(
416 fun({IP, Port, Transport, ProxyProtocol, EnableTURN}) ->
417 4 Opts1 = tls_opts(Transport) ++ Opts,
418 4 Opts2 = turn_opts(EnableTURN) ++ Opts1,
419 4 Opts3 = proxy_opts(ProxyProtocol) ++ Opts2,
420 4 ?LOG_DEBUG("Starting listener ~s (~s) with options:~n~p",
421 [eturnal_misc:addr_to_str(IP, Port), Transport,
422
:-(
Opts3]),
423 4 InfoArgs = [eturnal_misc:addr_to_str(IP, Port), Transport,
424 describe_listener(EnableTURN)],
425 4 case stun_listener:add_listener(IP, Port, Transport, Opts3) of
426 ok ->
427 4 ?LOG_INFO("Listening on ~s (~s) (~s)", InfoArgs);
428 {error, already_started} ->
429
:-(
?LOG_INFO("Already listening on ~s (~s) (~s)", InfoArgs);
430 {error, Reason} ->
431
:-(
exit({listener_failure, start, IP, Port, Transport,
432 Reason})
433 end,
434 4 {IP, Port, Transport}
435 end, get_opt(listen)).
436
437 -spec stop_listeners(state()) -> ok.
438 stop_listeners(#eturnal_state{listeners = Listeners}) ->
439 1 lists:foreach(
440 fun({IP, Port, Transport}) ->
441 4 case stun_listener:del_listener(IP, Port, Transport) of
442 ok ->
443 4 ?LOG_INFO("Stopped listening on ~s (~s)",
444 [eturnal_misc:addr_to_str(IP, Port),
445
:-(
Transport]);
446 {error, Reason} ->
447
:-(
exit({listener_failure, stop, IP, Port, Transport,
448 Reason})
449 end
450 end, Listeners).
451
452 -spec describe_listener(boolean()) -> binary().
453 describe_listener(true = _EnableTURN) ->
454 1 <<"STUN/TURN">>;
455 describe_listener(false = _EnableTURN) ->
456 3 <<"STUN only">>.
457
458 -spec opt_map() -> [{atom(), atom()}].
459 opt_map() ->
460 1 [{relay_ipv4_addr, turn_ipv4_address},
461 {relay_ipv6_addr, turn_ipv6_address},
462 {relay_min_port, turn_min_port},
463 {relay_max_port, turn_max_port},
464 {max_allocations, turn_max_allocations},
465 {max_permissions, turn_max_permissions},
466 {max_bps, shaper},
467 {realm, auth_realm},
468 {software_name, server_name}].
469
470 -spec opt_filter(Opt) -> {true, Opt} | false when Opt :: {option(), value()}.
471 opt_filter({turn_ipv6_address, undefined}) ->
472 1 false; % The 'stun' application currently wouldn't accept 'undefined'.
473 opt_filter(Opt) ->
474 8 {true, Opt}.
475
476 -spec turn_opts(boolean()) -> proplists:proplist().
477 turn_opts(EnableTURN) ->
478 4 case {EnableTURN, got_credentials(), got_relay_addr()} of
479 {true, true, true} ->
480 1 [{use_turn, true},
481 {auth_type, user}];
482 {_, _, _} ->
483 3 [{use_turn, false},
484 {auth_type, anonymous}]
485 end.
486
487 -spec proxy_opts(boolean()) -> proplists:proplist().
488 proxy_opts(true = _ProxyProtocol) ->
489
:-(
[proxy_protocol];
490 proxy_opts(false = _ProxyProtocol) ->
491 4 [].
492
493 %% This function can be removed in favor of opt_map/0 entries once the
494 %% 'blacklist' option is removed.
495 -spec blacklist_opts() -> proplists:proplist().
496 blacklist_opts() ->
497 1 case {eturnal:get_opt(blacklist),
498 eturnal:get_opt(blacklist_clients),
499 eturnal:get_opt(blacklist_peers)} of
500 {[], Clients, Peers} ->
501 1 [{turn_blacklist_clients, Clients},
502 {turn_blacklist_peers, Peers}];
503 {Blacklist, Clients, Peers} ->
504
:-(
?LOG_WARNING("The 'blacklist' option is deprecated"),
505
:-(
?LOG_WARNING("Use 'blacklist_clients' and/or 'blacklist_peers'"),
506
:-(
[{turn_blacklist_clients, lists:usort(Clients ++ Blacklist)},
507 {turn_blacklist_peers, lists:usort(Peers ++ Blacklist)}]
508 end.
509
510 %% This function can be removed in favor of opt_map/0 entries once the
511 %% 'whitelist' option is removed.
512 -spec whitelist_opts() -> proplists:proplist().
513 whitelist_opts() ->
514 1 case {eturnal:get_opt(whitelist),
515 eturnal:get_opt(whitelist_clients),
516 eturnal:get_opt(whitelist_peers)} of
517 {[], Clients, Peers} ->
518 1 [{turn_whitelist_clients, Clients},
519 {turn_whitelist_peers, Peers}];
520 {Whitelist, Clients, Peers} ->
521
:-(
?LOG_WARNING("The 'whitelist' option is deprecated"),
522
:-(
?LOG_WARNING("Use 'whitelist_clients' and/or 'whitelist_peers'"),
523
:-(
[{turn_whitelist_clients, lists:usort(Clients ++ Whitelist)},
524 {turn_whitelist_peers, lists:usort(Peers ++ Whitelist)}]
525 end.
526
527 -spec tls_opts(transport()) -> proplists:proplist().
528 -ifdef(old_inet_backend).
529 tls_opts(tls) ->
530 [{tls, true} | extra_tls_opts()];
531 tls_opts(auto) ->
532 exit({otp_too_old, transport, auto, 23});
533 tls_opts(_) ->
534 [].
535 -else.
536 tls_opts(tls) ->
537 1 [{tls, true} | extra_tls_opts()];
538 tls_opts(auto) ->
539 1 [{tls, optional} | extra_tls_opts()];
540 tls_opts(_) ->
541 2 [].
542 -endif.
543
544 -spec extra_tls_opts() -> proplists:proplist().
545 extra_tls_opts() ->
546 2 Opts = [{certfile, get_pem_file_path()},
547 {ciphers, get_opt(tls_ciphers)},
548 {protocol_options, get_opt(tls_options)}],
549 2 case get_opt(tls_dh_file) of
550 Path when is_binary(Path) ->
551
:-(
[{dhfile, Path} | Opts];
552 none ->
553 2 Opts
554 end.
555
556 %% Internal functions: configuration parsing.
557
558 -spec tls_enabled() -> boolean().
559 tls_enabled() ->
560 3 lists:any(fun({_IP, _Port, Transport, _ProxyProtocol, _EnableTURN}) ->
561 9 (Transport =:= tls) or (Transport =:= auto)
562 end, get_opt(listen)).
563
564 -spec turn_enabled() -> boolean().
565 turn_enabled() ->
566 1 lists:any(fun({_IP, _Port, _Transport, _ProxyProtocol, EnableTURN}) ->
567 1 EnableTURN =:= true
568 end, get_opt(listen)).
569
570 -spec got_credentials() -> boolean().
571 got_credentials() ->
572 4 case get_opt(secret) of
573 Secrets when is_list(Secrets) ->
574 4 lists:all(fun(Secret) ->
575 4 is_binary(Secret) and (byte_size(Secret) > 0)
576 end, Secrets);
577 Secret when is_binary(Secret), byte_size(Secret) > 0 ->
578
:-(
true;
579 undefined ->
580
:-(
map_size(get_opt(credentials)) > 0
581 end.
582
583 -spec got_relay_addr() -> boolean().
584 got_relay_addr() ->
585 5 case get_opt(relay_ipv4_addr) of
586 {_, _, _, _} ->
587 5 true;
588 undefined ->
589
:-(
false
590 end.
591
592 -spec check_turn_config() -> ok.
593 check_turn_config() ->
594 1 case turn_enabled() of
595 true ->
596 1 case {got_relay_addr(),
597 get_opt(relay_min_port),
598 get_opt(relay_max_port)} of
599 {_GotAddr, Min, Max} when Max =< Min ->
600
:-(
exit(turn_config_failure);
601 {false, _Min, _Max} ->
602
:-(
?LOG_WARNING("Specify a 'relay_ipv4_addr' to enable TURN");
603 {true, _Min, _Max} ->
604 1 ?LOG_DEBUG("TURN configuration seems fine")
605 end;
606 false ->
607
:-(
?LOG_DEBUG("TURN is disabled")
608 end.
609
610 -spec check_proxy_config() -> ok.
611 check_proxy_config() ->
612 1 case lists:any(
613 fun({_IP, _Port, Transport, ProxyProtocol, _EnableTURN}) ->
614 4 (Transport =:= udp) and (ProxyProtocol =:= true)
615 end, get_opt(listen)) of
616 true ->
617
:-(
exit(proxy_config_failure);
618 false ->
619 1 ok
620 end.
621
622 %% Internal functions: configuration reload.
623
624 -spec reload(state()) -> {ok, state()} | {error, term()}.
625 reload(State) ->
626 2 case conf:reload_file() of
627 ok ->
628 2 try check_pem_file() of
629 ok ->
630 1 ok = fast_tls:clear_cache(),
631 1 ?LOG_INFO("Using new TLS certificate");
632 unchanged ->
633 1 ?LOG_DEBUG("TLS certificate unchanged")
634 catch exit:Reason1 ->
635
:-(
?LOG_ERROR(format_error(Reason1))
636 end,
637 2 try {stop_modules(State), start_modules()} of
638 {ok, Modules} ->
639 2 ?LOG_DEBUG("Restarted modules"),
640 2 {ok, State#eturnal_state{modules = Modules}}
641 catch exit:Reason2 ->
642
:-(
?LOG_ERROR(format_error(Reason2)),
643
:-(
{ok, State}
644 end;
645 {error, Reason} = Err ->
646
:-(
?LOG_ERROR("Cannot reload configuration: ~ts",
647
:-(
[conf:format_error(Reason)]),
648
:-(
Err
649 end.
650
651 -spec apply_config_changes(state(), config_changes()) -> state().
652 apply_config_changes(State, {Changed, New, Removed} = ConfigChanges) ->
653
:-(
if length(Changed) > 0 ->
654
:-(
?LOG_DEBUG("Changed options: ~p", [Changed]);
655 length(Changed) =:= 0 ->
656
:-(
?LOG_DEBUG("No changed options")
657 end,
658
:-(
if length(Removed) > 0 ->
659
:-(
?LOG_DEBUG("Removed options: ~p", [Removed]);
660 length(Removed) =:= 0 ->
661
:-(
?LOG_DEBUG("No removed options")
662 end,
663
:-(
if length(New) > 0 ->
664
:-(
?LOG_DEBUG("New options: ~p", [New]);
665 length(New) =:= 0 ->
666
:-(
?LOG_DEBUG("No new options")
667 end,
668
:-(
try apply_logging_config_changes(ConfigChanges)
669 catch exit:Reason1 ->
670
:-(
?LOG_ERROR(format_error(Reason1))
671 end,
672
:-(
try apply_run_dir_config_changes(ConfigChanges)
673 catch exit:Reason2 ->
674
:-(
?LOG_ERROR(format_error(Reason2))
675 end,
676
:-(
try apply_relay_config_changes(ConfigChanges)
677 catch exit:Reason3 ->
678
:-(
?LOG_ERROR(format_error(Reason3))
679 end,
680
:-(
try apply_listener_config_changes(ConfigChanges, State)
681 catch exit:Reason4 ->
682
:-(
?LOG_ERROR(format_error(Reason4)),
683
:-(
State
684 end.
685
686 -spec apply_logging_config_changes(config_changes()) -> ok.
687 apply_logging_config_changes(ConfigChanges) ->
688
:-(
case logging_config_changed(ConfigChanges) of
689 true ->
690
:-(
?LOG_INFO("Using new logging configuration"),
691
:-(
ok = eturnal_logger:reconfigure();
692 false ->
693
:-(
?LOG_DEBUG("Logging configuration unchanged")
694 end.
695
696 -spec apply_run_dir_config_changes(config_changes()) -> ok.
697 apply_run_dir_config_changes(ConfigChanges) ->
698
:-(
case run_dir_config_changed(ConfigChanges) of
699 true ->
700
:-(
?LOG_INFO("Using new run directory configuration"),
701
:-(
ok = ensure_run_dir(),
702
:-(
case check_pem_file() of
703 ok ->
704
:-(
ok = fast_tls:clear_cache();
705 unchanged ->
706
:-(
ok
707 end;
708 false ->
709
:-(
?LOG_DEBUG("Run directory configuration unchanged")
710 end.
711
712 -spec apply_relay_config_changes(config_changes()) -> ok.
713 apply_relay_config_changes(ConfigChanges) ->
714
:-(
case relay_config_changed(ConfigChanges) of
715 true ->
716
:-(
?LOG_INFO("Using new TURN relay configuration"),
717
:-(
ok = log_relay_addresses();
718 false ->
719
:-(
?LOG_DEBUG("TURN relay configuration unchanged")
720 end.
721
722 -spec apply_listener_config_changes(config_changes(), state()) -> state().
723 apply_listener_config_changes(ConfigChanges, State) ->
724
:-(
case listener_config_changed(ConfigChanges) of
725 true ->
726
:-(
?LOG_INFO("Using new listener configuration"),
727
:-(
ok = check_turn_config(),
728
:-(
ok = check_proxy_config(),
729
:-(
ok = stop_listeners(State),
730
:-(
ok = timer:sleep(500),
731
:-(
Listeners = start_listeners(),
732
:-(
State#eturnal_state{listeners = Listeners};
733 false ->
734
:-(
?LOG_DEBUG("Listener configuration unchanged"),
735
:-(
State
736 end.
737
738 -spec logging_config_changed(config_changes()) -> boolean().
739 logging_config_changed({Changed, New, Removed}) ->
740 2 ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
741 2 LoggingKeys = [log_dir,
742 log_level,
743 log_rotate_size,
744 log_rotate_count],
745 2 lists:any(fun(Key) -> lists:member(Key, ModifiedKeys) end, LoggingKeys).
746
747 -spec run_dir_config_changed(config_changes()) -> boolean().
748 run_dir_config_changed({Changed, New, Removed}) ->
749 4 ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
750 4 RunDirKeys = [run_dir],
751 4 lists:any(fun(Key) -> lists:member(Key, ModifiedKeys) end, RunDirKeys).
752
753 -spec relay_config_changed(config_changes()) -> boolean().
754 relay_config_changed({Changed, New, Removed}) ->
755 4 ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
756 4 RelayKeys = [relay_ipv4_addr,
757 relay_ipv6_addr,
758 relay_min_port,
759 relay_max_port],
760 4 lists:any(fun(Key) -> lists:member(Key, ModifiedKeys) end, RelayKeys).
761
762 -spec listener_config_changed(config_changes()) -> boolean().
763 listener_config_changed({Changed, New, Removed} = ConfigChanges) ->
764 2 case relay_config_changed(ConfigChanges) or
765 run_dir_config_changed(ConfigChanges) of
766 true ->
767
:-(
true;
768 false ->
769 2 ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
770 2 ListenerKeys = [listen,
771 max_allocations,
772 max_permissions,
773 max_bps,
774 blacklist,
775 whitelist,
776 blacklist_clients,
777 whitelist_clients,
778 blacklist_peers,
779 whitelist_peers,
780 realm,
781 software_name,
782 tls_options,
783 tls_ciphers,
784 tls_dh_file],
785 2 lists:any(fun(Key) ->
786 19 lists:member(Key, ModifiedKeys)
787 end, ListenerKeys)
788 end.
789
790 %% Internal functions: PEM file handling.
791
792 -spec get_pem_file_path() -> file:filename_all().
793 get_pem_file_path() ->
794 6 filename:join(get_opt(run_dir), <<?PEM_FILE_NAME>>).
795
796 -spec check_pem_file() -> ok | unchanged.
797 check_pem_file() ->
798 3 case tls_enabled() of
799 true ->
800 3 OutFile = get_pem_file_path(),
801 3 case {get_opt(tls_crt_file), filelib:last_modified(OutFile)} of
802 {none, OutTime} when OutTime =/= 0 ->
803 1 ?LOG_DEBUG("Keeping PEM file (~ts)", [OutFile]),
804 1 unchanged;
805 {none, OutTime} when OutTime =:= 0 ->
806 2 ?LOG_WARNING("TLS enabled without 'tls_crt_file', creating "
807
:-(
"self-signed certificate"),
808 2 ok = create_self_signed(OutFile);
809 {CrtFile, OutTime} ->
810
:-(
case filelib:last_modified(CrtFile) of
811 CrtTime when CrtTime =< OutTime ->
812
:-(
?LOG_DEBUG("Keeping PEM file (~ts)", [OutFile]),
813
:-(
unchanged;
814 CrtTime when CrtTime =/= 0 -> % Assert to be true.
815
:-(
?LOG_DEBUG("Updating PEM file (~ts)", [OutFile]),
816
:-(
ok = import_pem_file(CrtFile, OutFile)
817 end
818 end;
819 false ->
820
:-(
?LOG_DEBUG("TLS not enabled, ignoring certificate configuration"),
821
:-(
unchanged
822 end.
823
824 -spec import_pem_file(binary(), file:filename_all()) -> ok.
825 import_pem_file(CrtFile, OutFile) ->
826
:-(
try
827
:-(
ok = touch(OutFile),
828
:-(
case get_opt(tls_key_file) of
829 KeyFile when is_binary(KeyFile) ->
830
:-(
ok = copy_file(KeyFile, OutFile, write);
831 none ->
832
:-(
?LOG_INFO("No 'tls_key_file' specified, assuming key in ~ts",
833
:-(
[CrtFile])
834 end,
835
:-(
ok = copy_file(CrtFile, OutFile, append)
836 catch error:{_, {error, Reason}} ->
837
:-(
exit({pem_failure, OutFile, Reason})
838 end.
839
840 -spec copy_file(file:name_all(), file:name_all(), write | append) -> ok.
841 copy_file(Src, Dst, Mode) ->
842
:-(
SrcMode = [read, binary, raw],
843
:-(
DstMode = [Mode, binary, raw],
844
:-(
{ok, _} = file:copy({Src, SrcMode}, {Dst, DstMode}),
845
:-(
?LOG_DEBUG("Copied ~ts into ~ts", [Src, Dst]).
846
847 -spec touch(file:filename_all()) -> ok.
848 touch(File) ->
849 2 {ok, Fd} = file:open(File, [append, binary, raw]),
850 2 ok = file:close(Fd),
851 2 ok = file:change_mode(File, 8#00600).
852
853 %% Internal functions: run directory.
854
855 -spec ensure_run_dir() -> ok.
856 ensure_run_dir() ->
857 1 RunDir = get_opt(run_dir),
858 1 case filelib:ensure_dir(filename:join(RunDir, <<"file">>)) of
859 ok ->
860 1 ?LOG_DEBUG("Using run directory ~ts", [RunDir]);
861 {error, Reason} ->
862
:-(
exit({run_dir_failure, create, RunDir, Reason})
863 end.
864
865 -spec clean_run_dir() -> ok.
866 clean_run_dir() ->
867 1 PEMFile = get_pem_file_path(),
868 1 case filelib:is_regular(PEMFile) of
869 true ->
870 1 case file:delete(PEMFile) of
871 ok ->
872 1 ?LOG_DEBUG("Removed ~ts", [PEMFile]);
873 {error, Reason} ->
874
:-(
exit({run_dir_failure, clean, PEMFile, Reason})
875 end;
876 false ->
877
:-(
?LOG_DEBUG("PEM file doesn't exist: ~ts", [PEMFile])
878 end.
879
880 %% Internal functions: error message formatting.
881
882 -spec format_error(atom() | tuple()) -> binary().
883 format_error({module_failure, Action, Mod, Reason}) ->
884
:-(
format("Failed to ~s ~s: ~p", [Action, Mod, Reason]);
885 format_error({dependency_failure, Mod, Dep}) ->
886
:-(
format("Dependency ~s is missing; install it below ~s, or point ERL_LIBS "
887 "to it, or disable ~s", [Dep, code:lib_dir(), Mod]);
888 format_error({listener_failure, Action, IP, Port, Transport, Reason}) ->
889
:-(
format("Cannot ~s listening on ~s (~s): ~s",
890 [Action, eturnal_misc:addr_to_str(IP, Port), Transport,
891 inet:format_error(Reason)]);
892 format_error({run_dir_failure, Action, RunDir, Reason}) ->
893
:-(
format("Cannot ~s run directory ~ts: ~ts",
894 [Action, RunDir, file:format_error(Reason)]);
895 format_error({pem_failure, File, Reason}) ->
896
:-(
format("Cannot create PEM file ~ts: ~ts",
897 [File, file:format_error(Reason)]);
898 format_error({otp_too_old, Key, Value, Vsn}) ->
899
:-(
format("Setting '~s: ~s' requires Erlang/OTP ~B or later",
900 [Key, Value, Vsn]);
901 format_error(proxy_config_failure) ->
902
:-(
<<"The 'proxy_protocol' ist not supported for 'udp'">>;
903 format_error(turn_config_failure) ->
904
:-(
<<"The 'relay_max_port' must be larger than the 'relay_min_port'">>;
905 format_error(_Unknown) ->
906
:-(
<<"Unknown error">>.
907
908 -spec format(io:format(), [term()]) -> binary().
909 format(Fmt, Data) ->
910
:-(
case unicode:characters_to_binary(io_lib:format(Fmt, Data)) of
911 Bin when is_binary(Bin) ->
912
:-(
Bin;
913 {_, _, _} = Err ->
914
:-(
erlang:error(Err)
915 end.
916
917 %% EUnit tests.
918
919 -ifdef(EUNIT).
920 config_change_test_() ->
921 4 [?_assert(logging_config_changed({[{log_level, info}], [], []})),
922 1 ?_assert(run_dir_config_changed({[{run_dir, <<"run">>}], [], []})),
923 1 ?_assert(relay_config_changed({[{relay_min_port, 50000}], [], []})),
924 1 ?_assert(listener_config_changed({[{max_bps, 42}], [], []})),
925 1 ?_assertNot(logging_config_changed({[{strict_expiry, false}], [], []})),
926 1 ?_assertNot(run_dir_config_changed({[{strict_expiry, false}], [], []})),
927 1 ?_assertNot(relay_config_changed({[{strict_expiry, false}], [], []})),
928 1 ?_assertNot(listener_config_changed({[{strict_expiry, false}], [], []}))].
929 -endif.
Line Hits Source